[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\ncustom: https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa6d3941c2c944e59831640fa0ece60d~tplv-k3u1fbpfcp-watermark.image\n"
  },
  {
    "path": ".gitignore",
    "content": "# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\nout/\n\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n*.log\n\n# Android Studio Navigation editor temp files\n.navigation/\n\n# Android Studio captures folder\ncaptures/\n\n# IntelliJ\n*.iml\n.idea/workspace.xml\n.idea/tasks.xml\n.idea/gradle.xml\n.idea/assetWizardSettings.xml\n.idea/dictionaries\n.idea/libraries\n.idea/caches\n\n# Keystore files\n# Uncomment the following line if you do not want to check your keystore files in.\n#*.jks\n\n# External native build folder generated in Android Studio 2.2 and later\n.externalNativeBuild\n\n# Google Services (e.g. APIs or Firebase)\ngoogle-services.json\n\n# Freeline\nfreeline.py\nfreeline/\nfreeline_project_description.json\n\n# fastlane\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots\nfastlane/test_output\nfastlane/readme.md\n\n.idea/codeStyles/Project.xml\n\n.idea/misc.xml\n\n.idea/modules.xml\n\n.idea/runConfigurations.xml\n\n.idea/vcs.xml\n\n.idea\n\ngradlew.bat\n\ngradlew\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 [yyyy] [name of copyright owner]\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": "# RxHttp\n\nEnglish | [中文文档](https://github.com/liujingxing/rxhttp/blob/master/README_zh.md)\n\n[![](https://jitpack.io/v/liujingxing/rxhttp.svg)](https://jitpack.io/#liujingxing/rxhttp)\n\n# [(RxHttp 3.0 更新指南，升级必看)](https://github.com/liujingxing/rxhttp/wiki/RxHttp-3.0-%E6%9B%B4%E6%96%B0%E6%8C%87%E5%8D%97%EF%BC%8C%E5%8D%87%E7%BA%A7%E5%BF%85%E7%9C%8B)\n\n# 使用RxHttp的知名App\n\n<img src=\"https://pp.myapp.com/ma_icon/0/icon_54221929_1770021797/256\" width=\"100\" height=\"100\"/> <img src=\"https://github.com/user-attachments/assets/1d263296-19b8-4e06-a19f-b656b0dcf4dc\" width=\"100\" height=\"100\"/>\n\n抖音旗下***汽水音乐***(app v18.1.0 设置/关于汽水音乐/开源软件声明 可查)\n\n<!--https://uinotes-img.oss-cn-shanghai.aliyuncs.com/logo/origin-webp/321899153012164010.webp-->\n\n\n# A type-safe HTTP client for Android. Written based on OkHttp\n\n\n![sequence_chart_en.jpg](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e25f9c6aa694b57bd43871eff95cecd~tplv-k3u1fbpfcp-watermark.image)\n\n\n<table>\n  <tr>\n     <th align=\"center\">Await</th>\n     <th align=\"center\">Flow</th>\n     <th align=\"center\">RxJava<br>(Kotlin)</th>\n     <th align=\"center\">RxJava<br>(Java)</th>\n  </tr>\n \n <tr>\n <td>\n  \n  ```java\n  //await return User\n  //tryAwait return User?\n  val user = RxHttp.get(\"/server/..\")\n      .add(\"key\", \"value\")\n      .toAwait<User>()\n      .await() \n  \n  //or awaitResult return kotlin.Result<T>\n  RxHttp.get(\"/server/..\")\n      .add(\"key\", \"value\")\n      .toAwait<User>()  \n      .awaitResult { \n          //Success\n      }.onFailure {  \n          //Failure\n      } \n  ```\n </td>\n  \n   <td>\n  \n  ```java\n  RxHttp.get(\"/server/..\")\n      .add(\"key\", \"value\")\n      .toFlow<User>()  \n      .catch { \n          //Failure\n      }.collect {  \n          //Success\n      }           \n  ```\n </td>\n    \n   <td>\n  \n  ```java\n  RxHttp.get(\"/server/..\")\n      .add(\"key\", \"value\")\n      .toObservable<User>()  \n      .subscribe({ \n          //Success\n      }, {  \n          //Failure\n      })           \n  ```\n </td>\n    \n   <td>\n  \n  ```java\n  RxHttp.get(\"/server/..\")\n      .add(\"key\", \"value\")\n      .toObservable(User.class)  \n      .subscribe(user - > { \n          //Success\n      }, throwable -> {  \n          //Failure\n      })           \n  ```\n </td>\n  \n </tr>\n \n </table>\n\n\n\n## 1、Feature\n\n- Support kotlin coroutines, RxJava2, RxJava3\n\n- Support Gson, Xml, ProtoBuf, FastJson and other third-party data parsing tools\n\n- Supports automatic closure of requests in FragmentActivity, Fragment, View, ViewModel, and any class\n\n- Support global encryption and decryption, add common parameters and headers, network cache, all support a request set up separately\n\n## 2、usage\n\n1、Adding dependencies and configurations\n\n### Required\n\n<details>\n<summary>1、Add jitpack to your build.gradle</summary>\n \n```java\nallprojects {\n    repositories {\n        maven { url \"https://jitpack.io\" }\n    }\n}\n```\n</details>\n<details>\n<summary>2、Java 8 or higher</summary>\n \n```java\nandroid {\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n}\n```\n</details>\n<details open>\n<summary>3、Add RxHttp dependency</summary>\n \n```kotlin\nplugins {\n    // kapt/ksp choose one\n    // id 'kotlin-kapt'\n    id 'com.google.devtools.ksp' version '2.3.4'\n}\n    \ndependencies {\n    def rxhttp_version = '3.5.1'\n    implementation 'com.squareup.okhttp3:okhttp:4.12.0'  \n    implementation \"com.github.liujingxing.rxhttp:rxhttp:$rxhttp_version\"\n    // ksp/kapt/annotationProcessor choose one\n    ksp \"com.github.liujingxing.rxhttp:rxhttp-compiler:$rxhttp_version\"\n }\n```\n</details>\n\n### Optional\n\n### 1、Converter\n```kotlin\nimplementation \"com.github.liujingxing.rxhttp:converter-serialization:$rxhttp_version\"\nimplementation \"com.github.liujingxing.rxhttp:converter-fastjson:$rxhttp_version\"\nimplementation \"com.github.liujingxing.rxhttp:converter-jackson:$rxhttp_version\"\nimplementation \"com.github.liujingxing.rxhttp:converter-moshi:$rxhttp_version\"\nimplementation \"com.github.liujingxing.rxhttp:converter-protobuf:$rxhttp_version\"\nimplementation \"com.github.liujingxing.rxhttp:converter-simplexml:$rxhttp_version\"\n```\n\n### 2、RxJava\n<details open>\n<summary>RxHttp + RxJava3</summary>\n \n ```java\nimplementation 'io.reactivex.rxjava3:rxjava:3.1.6'\nimplementation 'io.reactivex.rxjava3:rxandroid:3.0.2'\nimplementation 'com.github.liujingxing.rxlife:rxlife-rxjava3:2.2.2' //RxJava3, Automatic close request\n```\n \n</details>\n<details>\n<summary>RxHttp + RxJava2</summary>\n \n```java\nimplementation 'io.reactivex.rxjava2:rxjava:2.2.8'\nimplementation 'io.reactivex.rxjava2:rxandroid:2.1.1'\nimplementation 'com.github.liujingxing.rxlife:rxlife-rxjava2:2.2.2' //RxJava2, Automatic close request\n```\n</details>\n\n<details open>\n<summary>ksp passes the RxJava version</summary>\n \n```java\nksp {\n    arg(\"rxhttp_rxjava\", \"3.1.6\")\n}\n```\n \n</details>\n\n<details>\n<summary>Kapt passes the RxJava version</summary>\n \n```java\nkapt {\n    arguments {\n        arg(\"rxhttp_rxjava\", \"3.1.6\")\n    }\n}\n```\n \n</details>\n \n<details>\n<summary>javaCompileOptions passes the RxJava version</summary>\n \n```java\nandroid {\n    defaultConfig {\n        javaCompileOptions {\n            annotationProcessorOptions {\n                arguments = [\n                    rxhttp_rxjava: '3.1.6', \n                ]\n            }\n        }\n    }\n}\n```\n \n</details>\n\n\n### 3、set RxHttp class package name\n\n<details open>\n<summary>ksp pass package name</summary>\n \n```java\nksp {\n     arg(\"rxhttp_package\", \"rxhttp.xxx\")\n}\n```\n    \n<details>\n<summary>kapt pass package name</summary>\n \n```java\nkapt {\n    arguments {\n       arg(\"rxhttp_package\", \"rxhttp.xxx\") \n    }\n}\n```\n \n</details>\n \n<details>\n<summary>javaCompileOptions pass package name</summary>\n \n```java\nandroid {\n    defaultConfig {\n        javaCompileOptions {\n            annotationProcessorOptions {\n                arguments = [\n                    rxhttp_package: 'rxhttp.xxx'\n                ]\n            }\n        }\n    }\n}\n```\n</details>\n\n\n**Finally, rebuild the project, which is necessary**\n\n2、Initialize the SDK\n\nThis step is optional\n\n```java\nRxHttpPlugins.init(OkHttpClient)  \n    .setDebug(boolean)  \n    .setOnParamAssembly(Consumer)\n    ....\n```\n\n3、Configuration BaseUrl\n\nThis step is optional\n\n```java\npublic class Url {\n\n    //Add the @defaultDomain annotation to BASE_URL\n    @DefaultDomain\n    public static BASE_URL = \"https://...\"\n}\n```\n\n4、Perform the requested\n\n```java\n// java\nRxHttp.get(\"/service/...\")   //1、You can choose get,postFrom,postJson etc\n    .addQuery(\"key\", \"value\")               //add query param\n    .addHeader(\"headerKey\", \"headerValue\")  //add request header\n    .toObservable(Student.class)  //2、Use the toXxx method to determine the return value type, customizable\n    .subscribe(student -> {  //3、Subscribing observer\n        //Success callback，Default IO thread\n    }, throwable -> {\n        //Abnormal callback\n    });\n\n// kotlin \nRxHttp.postForm(\"/service/...\")          //post FormBody\n    .add(\"key\", \"value\")                 //add param to body\n    .addQuery(\"key1\", \"value1\")          //add query param\n    .addFile(\"file\", File(\".../1.png\"))  //add file to body\n    .toObservable<Student>()           \n    .subscribe({ student ->       \n        //Default IO thread\n    }, { throwable ->\n        \n    })\n\n// kotlin coroutine\nval students = RxHttp.postJson(\"/service/...\")  //1、post {application/json; charset=utf-8}\n    .toAwaitList<Student>()                          //2、Use the toXxx method to determine the return value type, customizable\n    .await()                                    //3、Get the return value, await is the suspend method\n```\n\n## 3、Advanced usage\n\n 1、Close the request\n\n```java\n//In Rxjava2 , Automatic close request\nRxHttp.get(\"/service/...\")\n    .toObservableString()\n    .as(RxLife.as(this))  //The Activity destroys and automatically closes the request\n    .subscribe(s -> {\n        //Default IO thread\n    }, throwable -> {\n\n    });\n\n//In Rxjava3 , Automatic close request\nRxHttp.get(\"/service/...\")\n    .toObservableString()\n    .to(RxLife.to(this))  //The Activity destroys and automatically closes the request\n    .subscribe(s -> {\n        //Default IO thread\n    }, throwable -> {\n        \n    });\n\n\n//In RxJava2/RxJava3, close the request manually\nDisposable disposable = RxHttp.get(\"/service/...\")\n    .toObservableString()\n    .subscribe(s -> {\n        //Default IO thread\n    }, throwable -> {\n        \n    });\n\ndisposable.dispose(); //Close the request at the appropriate time\n```\n\n## 4、ProGuard\n\nIf you are using RxHttp v2.2.8 or above the shrinking and obfuscation rules are included automatically.\nOtherwise you must manually add the options in [rxhttp.pro](https://github.com/liujingxing/rxhttp/blob/master/rxhttp/src/main/resources/META-INF/proguard/rxhttp.pro).\n\n## 5、Donations\n\nIf this project helps you a lot and you want to support the project's development and maintenance of this project, feel free to scan the following QR code for donation. Your donation is highly appreciated. Thank you!\n\n![donations.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa6d3941c2c944e59831640fa0ece60d~tplv-k3u1fbpfcp-watermark.image?)\n\n# Licenses\n\n```\nCopyright 2019 liujingxing\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou 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"
  },
  {
    "path": "README_zh.md",
    "content": "# RxHttp\n\n[English](https://github.com/liujingxing/rxhttp/blob/master/README.md) | 中文文档\n\n[![](https://jitpack.io/v/liujingxing/rxhttp.svg)](https://jitpack.io/#liujingxing/rxhttp) \n![](https://img.shields.io/badge/API-9+-blue.svg)\n[![](https://img.shields.io/badge/change-更新日志-success.svg)](https://github.com/liujingxing/rxhttp/wiki/%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97)\n[![](https://img.shields.io/badge/FAQ-常见问题-success.svg)](https://github.com/liujingxing/rxhttp/wiki/FAQ)\n[![](https://img.shields.io/badge/掘金-@刘一刀-blue.svg)](https://juejin.cn/user/272334612601559/posts)\n<!--[![](https://img.shields.io/badge/QQ群-378530627-red.svg)](https://jq.qq.com/?_wv=1027&k=E53Hakvv)-->\n\n# [(RxHttp 3.0 更新指南，升级必看)](https://github.com/liujingxing/rxhttp/wiki/RxHttp-3.0-%E6%9B%B4%E6%96%B0%E6%8C%87%E5%8D%97%EF%BC%8C%E5%8D%87%E7%BA%A7%E5%BF%85%E7%9C%8B)\n\n# 使用RxHttp的知名App\n\n<img src=\"https://pp.myapp.com/ma_icon/0/icon_54221929_1770021797/256\" width=\"100\" height=\"100\"/> <img src=\"https://github.com/user-attachments/assets/1d263296-19b8-4e06-a19f-b656b0dcf4dc\" width=\"100\" height=\"100\"/>\n\n抖音旗下***汽水音乐***(app v18.1.0 设置/关于汽水音乐/开源软件声明 可查)\n\n\n***加我微信 ljx-studio 拉你进微信群(备注RxHttp)*** \n\n# 1、主要优势\n\n  ***1. 30秒即可上手，学习成本极低***\n\n  ***2. 史上最优雅的支持 Kotlin 协程***\n\n  ***3. 史上最优雅的处理多个BaseUrl及动态BaseUrl***\n\n  ***4. 史上最优雅的对错误统一处理，且不打破Lambda表达式***\n\n  ***5. 史上最优雅的文件上传/下载/断点下载/进度监听，已适配Android 10***\n\n  ***6. 支持Gson、Xml、ProtoBuf、FastJson等第三方数据解析工具***\n\n  ***7. 支持Get、Post、Put、Delete等任意请求方式，可自定义请求方式***\n\n  ***8. 支持在Activity/Fragment/View/ViewModel/任意类中，自动关闭请求***\n\n  ***9. 支持全局加解密、添加公共参数及头部、网络缓存，均支持对某个请求单独设置***\n\n# 2、请求三部曲\n\n![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/29bdab825c4f42c0983c58777f3af675~tplv-k3u1fbpfcp-watermark.image)\n\n***代码表示，\n[toObservableXxx、toAwaitXxx、toFlowXxx方法介绍点这里](https://github.com/liujingxing/rxhttp/wiki/RxJava%E3%80%81Await%E3%80%81Flow-%E5%AF%B9%E5%BA%94%E7%9A%84-asXxx%E3%80%81toXxx%E3%80%81toFlowXxx%E6%96%B9%E6%B3%95%E4%BB%8B%E7%BB%8D)***\n\n<table>\n  <tr>\n     <th align=\"center\">Await</th>\n     <th align=\"center\">Flow</th>\n     <th align=\"center\">RxJava<br>(Kotlin)</th>\n     <th align=\"center\">RxJava<br>(Java)</th>\n  </tr>\n \n <tr>\n <td>\n  \n  ```java\n  //同步式写法\n  val user = RxHttp.get(\"/server/..\")\n      .add(\"key\", \"value\")\n      .toAwait<User>()\n      .await() //tryAwait return User?\n  \n  //回调式写法\n  RxHttp.get(\"/server/..\")\n      .add(\"key\", \"value\")\n      .toAwait<User>()  \n      .awaitResult { \n          //成功回调   \n      }.onFailure {  \n          //异常回调  \n      } \n  ```\n </td>\n  \n   <td>\n  \n  ```java\n  RxHttp.get(\"/server/..\")\n      .add(\"key\", \"value\")\n      .toFlow<User>()  \n      .catch { \n          //异常回调   \n      }.collect {  \n          //成功回调  \n      }           \n  ```\n </td>\n    \n   <td>\n  \n  ```java\n  RxHttp.get(\"/server/..\")\n      .add(\"key\", \"value\")\n      .toObservable<User>()  \n      .subscribe({ \n          //成功回调   \n      }, {  \n          //异常回调  \n      })           \n  ```\n </td>\n    \n   <td>\n  \n  ```java\n  RxHttp.get(\"/server/..\")\n      .add(\"key\", \"value\")\n      .toObservable(User.class)  \n      .subscribe(user - > { \n          //成功回调   \n      }, throwable -> {  \n          //异常回调  \n      })           \n  ```\n </td>\n  \n </tr>\n \n </table>\n\n***RxHttp与Retrofit对比***\n\n| 功能说明 | RxHttp | [Retrofit](https://github.com/square/retrofit) |\n| --- | :---: | :---: |\n| 版本| v3.2.6| v2.9.0 |\n| 状态| 维护中| 维护中 |\n| 标准RESTful风格| ✅ | ✅ |\n| 学习成本| 低 | 高|\n| 扩展性| 高| 高|\n| 源码大小| 73k | 75k |\n| jar包大小| 210k  | 123k |\n| RxJava| RxJava  ❌<br>RxJava2✅<br>RxJava3✅| RxJava  ✅<br>RxJava2✅<br>RxJava3✅|\n| Kotlin协程| ✅ | ✅ |\n| Flow流| ✅ | ✅ |\n| Converter| Gson✅<br> Jackson✅<br> fastJson✅<br> Moshi✅<br> Protobuf✅<br> simplexml✅<br> kotlinx.serialization✅<br> 自定义✅<br> | Gson✅<br> Jackson✅<br> fastJson✅<br> Moshi✅<br> Protobuf✅<br> simplexml✅<br> kotlinx.serialization✅<br> 自定义✅<br> |\n| 关闭请求 | 手动✅<br>自动✅<br>批量✅| 手动✅<br>自动✅<br>批量✅ |\n| 文件上传/下载/进度监听| ✅ | ❌需再次封装|\n| Android 10分区存储| ✅ | ❌需再次封装|\n| 公共参数| ✅| ❌需再次封装 |\n| 多域名/动态域名| ✅好用 | ✅一般 |\n| 日志打印| ✅|  ✅ |\n| Json数据格式化输出| ✅| ❌需再次封装 |\n| 业务code统一判断| ✅ | ❌需再次封装|\n| 请求缓存| ✅ | ❌需再次封装|\n| 全局加解密| ✅ | ❌需再次封装 |\n| 部分字段解密 | ✅ | ❌需再次封装 |\n\n**说明**\n\n也许你有会有疑问，RxHttp源码大小仅比retrofit大6k的情况下，jar包大小为何会大一倍多？功能太多导致的代码臃肿？并不是，而是由kotlin导致的，在RxHttp内部，为了支持`Await/Flow`，运用了大量的kotlin内联方法及扩展方法，这些方法在编译为字节码后，都会相对较大，其中[AwaitTransform.kt](https://github.com/liujingxing/rxhttp/blob/master/rxhttp/src/main/java/rxhttp/AwaitTransform.kt)、[CallFactoryToAwait.kt](https://github.com/liujingxing/rxhttp/blob/master/rxhttp/src/main/java/rxhttp/CallFactoryToAwait.kt)、[CallFactoryToFlow.kt](https://github.com/liujingxing/rxhttp/blob/master/rxhttp/src/main/java/rxhttp/CallFactoryToFlow.kt)这3个kotlin文件，编译字节码后，就接近70k\n\n\n# 3、相关文档\n\n30秒上手教程：[30秒上手新一代Http请求神器RxHttp](https://juejin.im/post/5cfcbbcbe51d455a694f94df)\n\nFlow文档：[RxHttp + Flow 三步搞定任意请求](https://juejin.cn/post/7017604875764629540)\n\nAwait文档：[RxHttp ，比Retrofit 更优雅的协程体验](https://juejin.im/post/5e77604fe51d4527066eb81a#heading-2)\n\nRxJava及核心Api介绍：[RxHttp 让你眼前一亮的Http请求框架](https://juejin.im/post/5ded221a518825125d14a1d4)\n\nwiki详细文档：https://github.com/liujingxing/rxhttp/wiki  (此文档会持续更新)\n\n## [(RxHttp 3.0 更新指南，升级必看)](https://github.com/liujingxing/rxhttp/wiki/RxHttp-3.0-%E6%9B%B4%E6%96%B0%E6%8C%87%E5%8D%97%EF%BC%8C%E5%8D%87%E7%BA%A7%E5%BF%85%E7%9C%8B)\n \n自动关闭请求用到的RxLife类，详情请查看[RxLife库](https://github.com/liujingxing/rxlife)\n\n\n# 4、上手准备\n\n***1、RxHttp依赖有3种方式，选择其中一种就好，\n[ksp、kapt、annotationProcessor 如何选择点击这里](https://github.com/liujingxing/rxhttp/wiki/ksp%E3%80%81kapt%E3%80%81annotationProcessor-%E7%94%A8%E6%B3%95%E5%8F%8A%E5%8C%BA%E5%88%AB)***\n\n***2、asXxx方法内部通过RxJava实现，如需使用，需额外依赖RxJava并告知RxHttp你依赖的Rxjava版本***\n\n***3、RxHttp已适配`OkHttp 3.12.0 - v4.12.0`版本(4.3.0除外), 如需兼容21以下，请依赖`OkHttp 3.12.x`，该版本最低要求 API 9***\n\n***4、[Maven依赖点击这里](https://github.com/liujingxing/rxhttp/blob/master/maven_dependency.md)***\n\n## 4.1、必须\n\n<details open>\n<summary>annotationProcessor依赖</summary>\n \n```gradle\n//1、项目的build.gradle文件\nallprojects {\n    repositories {\n        maven { url \"https://jitpack.io\" }\n    }\n}\n//2、java 8或更高\nandroid {\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n}\n//3、添加依赖\ndependencies {\n    def rxhttp_version = '3.5.1'\n    implementation 'com.squareup.okhttp3:okhttp:4.12.0'  \n    implementation \"com.github.liujingxing.rxhttp:rxhttp:$rxhttp_version\"\n    annotationProcessor \"com.github.liujingxing.rxhttp:rxhttp-compiler:$rxhttp_version\"\n}\n```\n\n</details>\n\n<details>\n<summary>kapt依赖</summary>\n \n```gradle\n//1、项目的build.gradle文件\nallprojects {\n    repositories {\n        maven { url \"https://jitpack.io\" }\n    }\n}\n//2、java 8或更高\nandroid {\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n}\n//3、添加插件及依赖\nplugins {\n    id 'kotlin-kapt'\n}\n \ndependencies {\n    def rxhttp_version = '3.5.1'\n    implementation 'com.squareup.okhttp3:okhttp:4.12.0'  \n    implementation \"com.github.liujingxing.rxhttp:rxhttp:$rxhttp_version\"\n    kapt \"com.github.liujingxing.rxhttp:rxhttp-compiler:$rxhttp_version\"\n}\n```\n\n</details>\n\n<details>\n<summary>ksp依赖</summary>\n \n```gradle\n//1、项目的build.gradle文件\nallprojects {\n    repositories {\n        maven { url \"https://jitpack.io\" }\n    }\n}\n//2、java 8或更高，及配置sourceSets\nandroid {\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n}\n//3、添加插件及依赖\nplugins {\n    id 'com.google.devtools.ksp' version '2.3.4'\n}\n \ndependencies {\n    def rxhttp_version = '3.5.1'\n    implementation 'com.squareup.okhttp3:okhttp:4.12.0'  \n    implementation \"com.github.liujingxing.rxhttp:rxhttp:$rxhttp_version\"\n    ksp \"com.github.liujingxing.rxhttp:rxhttp-compiler:$rxhttp_version\"\n}\n```\n \n</details>\n\n\n\n\n## 4.2、可选\n\n### 4.2.1、配置RxJava\n\n如果你需要结合`toObservableXxx`方法发请求，就需要额外依赖`RxJava`，并且告知`rxhttp`你依赖的`RxJava`版本号\n\n- ***依赖RxJava，RxJava2/RxJava3选其一***\n\n```java\n//RxJava3 \nimplementation 'io.reactivex.rxjava3:rxjava:3.1.6'\nimplementation 'io.reactivex.rxjava3:rxandroid:3.0.2'\nimplementation 'com.github.liujingxing.rxlife:rxlife-rxjava3:2.2.2' //管理RxJava3生命周期，页面销毁，关闭请求\n\n//RxJava2\nimplementation 'io.reactivex.rxjava2:rxjava:2.2.8'\nimplementation 'io.reactivex.rxjava2:rxandroid:2.1.1'\nimplementation 'com.github.liujingxing.rxlife:rxlife-rxjava2:2.2.2' //管理RxJava2生命周期，页面销毁，关闭请求\n```\n\n- ***通过ksp/kapt/annotationProcessor,其中一种方式传递RxJava版本号***\n<details open>\n<summary>通过ksp传递RxJava版本</summary>\n \n```java\nksp {\n    arg(\"rxhttp_rxjava\", \"3.1.6\")\n}\n```\n \n</details>\n\n<details>\n<summary>通过kapt传递RxJava版本</summary>\n \n```java\nkapt {\n    arguments {\n        arg(\"rxhttp_rxjava\", \"3.1.6\")\n    }\n}\n```\n \n</details>\n \n<details>\n<summary>通过annotationProcessor传递RxJava版本</summary>\n \n```java\nandroid {\n    defaultConfig {\n        javaCompileOptions {\n            annotationProcessorOptions {\n                arguments = [\n                    //使用asXxx方法时必须，传入你依赖的RxJava版本\n                    rxhttp_rxjava: '3.1.6', \n                ]\n            }\n        }\n    }\n}\n```\n \n</details>\n\n\n### 4.2.2、配置Converter\n\n```kotlin\n//非必须，根据自己需求选择 RxHttp默认内置了GsonConverter\nimplementation \"com.github.liujingxing.rxhttp:converter-serialization:$rxhttp_version\"\nimplementation \"com.github.liujingxing.rxhttp:converter-fastjson:$rxhttp_version\"\nimplementation \"com.github.liujingxing.rxhttp:converter-jackson:$rxhttp_version\"\nimplementation \"com.github.liujingxing.rxhttp:converter-moshi:$rxhttp_version\"\nimplementation \"com.github.liujingxing.rxhttp:converter-protobuf:$rxhttp_version\"\nimplementation \"com.github.liujingxing.rxhttp:converter-simplexml:$rxhttp_version\"\n```\n\n### 4.2.3、指定RxHttp相关类的存放目录\n\n如果你有多个module依赖`rxhttp-compiler`(不建议这么做，一般base module依赖就好)，则每个module下都会生成`RxHttp`类，且目录相同，在运行或打包时，就会出现RxHttp类冲突的问题，此时就需要你自定义RxHttp的存放目录，也就是RxHttp类的包名，`ksp/kapt/annotationProcessor`选择其中一种方式就好\n\n<details open>\n<summary>通过ksp指定RxHttp相关类包名</summary>\n \n```java\nksp {\n    arg(\"rxhttp_package\", \"rxhttp\")  //指定RxHttp类包名，可随意指定 \n}\n```\n \n</details>\n\n<details>\n<summary>通过kapt指定RxHttp相关类包名</summary>\n \n```java\n \nkapt {\n    arguments {\n        arg(\"rxhttp_package\", \"rxhttp\")  //指定RxHttp类包名，可随意指定\n    }\n}\n```\n \n</details>\n \n<details>\n<summary>通过javaCompileOptions指定RxHttp相关类包名</summary>\n \n```java\nandroid {\n    defaultConfig {\n        javaCompileOptions {\n            annotationProcessorOptions {\n                arguments = [\n                    rxhttp_package: 'rxhttp',  //指定RxHttp类包名，可随意指定\n                ]\n            }\n        }\n    }\n}\n```\n</details>\n\n\n最后，***rebuild一下(此步骤是必须的)*** ，就会自动生成RxHttp类\n\n\n# 5、混淆\n\n- `RxHttp v2.2.8`及以上版本，无需添加任何混淆规则，将你自己的Bean类Keep下就好\n- `RxHttp v2.2.8`以下版本，将[RxHttp 混淆规则](https://github.com/liujingxing/rxhttp/wiki/关于混淆)，添加到自己项目中，并将你自己的Bean类Keep下\n\n# 6、Demo演示\n<img src=\"https://github.com/liujingxing/rxhttp/blob/master/screen/demo.gif\" width = \"360\" height = \"640\" />\n\n\n> 更多功能，请[下载apk](https://github.com/liujingxing/rxhttp/blob/master/screen/app-debug.apk)体验\n\n# 7、Donations\n如果它对你帮助很大，并且你很想支持库的后续开发和维护，那么你可以扫下方二维码随意打赏我，就当是请我喝杯咖啡或是啤酒，开源不易，感激不尽\n\n![donations.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa6d3941c2c944e59831640fa0ece60d~tplv-k3u1fbpfcp-watermark.image?)\n\n\n# Licenses\n```\nCopyright 2019 liujingxing\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou 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"
  },
  {
    "path": "app/.gitignore",
    "content": "#/build\n/release\n"
  },
  {
    "path": "app/build.gradle",
    "content": "plugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.kotlin.android)\n    id 'kotlin-kapt'\n    id 'com.google.devtools.ksp' version libs.versions.ksp\n}\n\nandroid {\n    \n    namespace 'com.example.httpsender'\n\n    compileSdk 35\n    defaultConfig {\n        applicationId \"com.example.rxhttp\"\n        minSdk 23\n        targetSdk 35\n        versionCode 1\n        versionName \"1.0\"\n\n        multiDexEnabled true\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_17\n        targetCompatibility JavaVersion.VERSION_17\n    }\n\n    buildFeatures {\n        buildConfig = true\n        dataBinding = true\n    }\n    lint {\n        abortOnError false\n        checkReleaseBuilds false\n    }\n}\n\nksp {\n    arg(\"rxhttp_rxjava\", libs.versions.rxjava.get())\n//        arg(\"rxhttp_package\", \"rxhttp\")\n}\n\nkapt {\n    arguments {\n        arg(\"rxhttp_rxjava\", libs.versions.rxjava.get()) //可传入rxjava2、rxjava3或具体版本号，如 3.1.1\n//            arg(\"rxhttp_package\", \"rxhttp\") //设置RxHttp相关类的包名，多module依赖时，需要配置不同的包名\n    }\n}\n\n\ndependencies {\n    implementation libs.androidx.multidex\n    implementation libs.androidx.appcompat\n    implementation libs.androidx.recyclerview\n    implementation libs.androidx.constraintlayout\n    implementation libs.github.magicindicator\n    implementation libs.androidx.fragment.ktx\n    implementation libs.androidx.lifecycle.runtime.ktx\n    implementation libs.androidx.lifecycle.livedata.ktx\n    implementation libs.androidx.lifecycle.viewmodel.ktx\n    implementation libs.androidx.lifecycle.service\n\n    implementation projects.rxhttp\n    ksp projects.rxhttpCompiler\n//    kapt projects.rxhttpCompiler\n\n    implementation libs.okhttp\n    testImplementation libs.mockwebserver\n\n//    implementation libs.rxhttp\n//    ksp libs.rxhttp.compiler\n//    kapt libs.rxhttp.compiler\n\n    //管理RxJava及生命周期，Activity/Fragment 销毁，自动关闭未完成的请求\n    implementation libs.github.rxlife.rxjava3\n    implementation libs.rxandroid\n    implementation libs.rxjava\n\n//    implementation libs.rxhttp.converter.serialization\n//    implementation libs.rxhttp.converter.fastjson\n//    implementation libs.rxhttp.converter.jackson\n//    implementation libs.rxhttp.converter.moshi\n//    implementation libs.rxhttp.converter.protobuf\n//    implementation libs.rxhttp.converter.simplexml\n\n    implementation projects.rxhttpConverter.converterSerialization\n    implementation projects.rxhttpConverter.converterFastjson\n    implementation projects.rxhttpConverter.converterSimplexml\n    implementation projects.rxhttpConverter.converterProtobuf\n    implementation projects.rxhttpConverter.converterMoshi\n    implementation projects.rxhttpConverter.converterJackson\n\n    testImplementation libs.junit\n    androidTestImplementation libs.androidx.junit\n    androidTestImplementation libs.androidx.espresso.core\n    implementation libs.utilcodex\n\n    testImplementation libs.kotlin.compile.testing.ksp\n    testImplementation libs.kotlinpoet\n    testImplementation libs.kotlinpoet.ksp\n    testImplementation projects.rxhttpCompiler\n}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keepclassmembers class com.example.httpsender.entity.** {\n    <init>();  #R8 full mode下, 默认构造方法不保留\n    !transient <fields>;\n}\n\n# With R8 full mode generic signatures are stripped for classes that are not kept.\n-keep,allowobfuscation,allowshrinking class com.example.httpsender.entity.Response\n-keep,allowobfuscation,allowshrinking class com.example.httpsender.entity.PageList\n\n\n#依赖simple-xml后打包失败，需加入以下规则\n-dontwarn android.content.res.**\n\n#依赖fastjson后打包失败，需加入以下规则\n-dontwarn javax.ws.rs.**\n\n# Please add these rules to your existing keep rules in order to suppress warnings.\n# This is generated automatically by the Android Gradle plugin.\n-dontwarn com.google.common.collect.ArrayListMultimap\n-dontwarn com.google.common.collect.Multimap\n-dontwarn java.awt.Color\n-dontwarn java.awt.Font\n-dontwarn java.awt.Point\n-dontwarn java.awt.Rectangle\n-dontwarn javax.money.CurrencyUnit\n-dontwarn javax.money.Monetary\n-dontwarn javax.ws.rs.Consumes\n-dontwarn javax.ws.rs.Produces\n-dontwarn javax.ws.rs.core.Response\n-dontwarn javax.ws.rs.core.StreamingOutput\n-dontwarn javax.ws.rs.ext.MessageBodyReader\n-dontwarn javax.ws.rs.ext.MessageBodyWriter\n-dontwarn javax.ws.rs.ext.Provider\n-dontwarn org.glassfish.jersey.internal.spi.AutoDiscoverable\n-dontwarn org.javamoney.moneta.Money\n-dontwarn org.joda.time.DateTime\n-dontwarn org.joda.time.DateTimeZone\n-dontwarn org.joda.time.Duration\n-dontwarn org.joda.time.Instant\n-dontwarn org.joda.time.LocalDate\n-dontwarn org.joda.time.LocalDateTime\n-dontwarn org.joda.time.LocalTime\n-dontwarn org.joda.time.Period\n-dontwarn org.joda.time.ReadablePartial\n-dontwarn org.joda.time.format.DateTimeFormat\n-dontwarn org.joda.time.format.DateTimeFormatter\n-dontwarn springfox.documentation.spring.web.json.Json"
  },
  {
    "path": "app/src/androidTest/java/com/example/httpsender/ExampleInstrumentedTest.java",
    "content": "package com.example.httpsender;\n\nimport android.content.Context;\nimport android.util.Log;\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.platform.app.InstrumentationRegistry;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport io.reactivex.rxjava3.disposables.Disposable;\nimport rxhttp.wrapper.param.RxHttp;\n\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();\n        Disposable subscribe = RxHttp.get(\"https://www.wanandroid.com/article/list/0/json\")\n            .toObservableString()\n            .subscribe(s -> {\n                System.out.println(s);\n            }, throwable -> {\n                Log.e(\"LJX\", \"useAppContext\");\n            });\n        \n        while (!subscribe.isDisposed()) {\n            try {\n                Thread.sleep(100);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n\n    <application\n        android:name=\".AppHolder\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:networkSecurityConfig=\"@xml/network_config\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".MainActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/AppHolder.java",
    "content": "package com.example.httpsender;\n\nimport android.app.Application;\nimport android.content.Context;\n\nimport androidx.multidex.MultiDex;\n\n/**\n * User: ljx\n * Date: 2019/3/31\n * Time: 09:11\n */\n//@HiltAndroidApp\npublic class AppHolder extends Application {\n\n    private static AppHolder instance;\n\n    public static AppHolder getInstance() {\n        return instance;\n    }\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        instance = this;\n        RxHttpManager.init(this);\n    }\n\n    protected void attachBaseContext(Context base) {\n        super.attachBaseContext(base);\n        MultiDex.install(this);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/DownloadMultiAdapter.java",
    "content": "package com.example.httpsender;\n\nimport android.annotation.SuppressLint;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.ProgressBar;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.recyclerview.widget.RecyclerView;\n\nimport com.example.httpsender.DownloadMultiAdapter.MyViewHolder;\nimport com.example.httpsender.entity.DownloadTask;\nimport com.example.httpsender.vm.MultiTaskDownloader;\n\nimport java.text.DecimalFormat;\nimport java.util.List;\nimport java.util.Locale;\n\n/**\n * User: ljx\n * Date: 2019-06-07\n * Time: 11:10\n */\npublic class DownloadMultiAdapter extends RecyclerView.Adapter<MyViewHolder> {\n\n    private OnItemClickListener<DownloadTask> mOnItemClickListener;\n\n    private List<DownloadTask> mDownloadTasks;\n\n    public DownloadMultiAdapter(List<DownloadTask> downloadTasks) {\n        mDownloadTasks = downloadTasks;\n    }\n\n    @NonNull\n    @Override\n    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {\n        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.download_multi_adapter, viewGroup, false);\n        return new MyViewHolder(view);\n    }\n\n    @SuppressLint(\"DefaultLocale\")\n    @Override\n    public void onBindViewHolder(@NonNull MyViewHolder viewHolder, int i) {\n        DownloadTask data = mDownloadTasks.get(i);\n        viewHolder.progressBar.setProgress((int) (data.getProgress()*100));\n        viewHolder.tvProgress.setText(String.format(\"%.1f%%\", data.getProgress() * 100));\n        viewHolder.btPause.setOnClickListener(v -> {\n            mOnItemClickListener.onItemClick(v, data, i);\n        });\n        String currentSize = new DecimalFormat(\"0.0\").format(data.getCurrentSize() * 1.0f / 1024 / 1024);\n        String totalSize = new DecimalFormat(\"0.0\").format(data.getTotalSize() * 1.0f / 1024 / 1024);\n        viewHolder.tvSize.setText(String.format(\"%sM/%sM\", currentSize, totalSize));\n\n        int state = data.getState();\n        if (state == MultiTaskDownloader.IDLE) {\n            viewHolder.tvWaiting.setText(\"未开始\");\n            viewHolder.btPause.setText(\"开始\");\n        } else if (state == MultiTaskDownloader.WAITING) {\n            viewHolder.tvWaiting.setText(\"等待中..\");\n            viewHolder.btPause.setText(\"取消\");\n        } else if (state == MultiTaskDownloader.DOWNLOADING) {\n            viewHolder.tvWaiting.setText(getSpeed(data.getSpeed()));\n            long remainingTime = data.getRemainingTime();\n            if (remainingTime > 0) {\n                viewHolder.tvWaiting.append(\" 预估还需\" + getRemainingTime(remainingTime));\n            }\n            viewHolder.btPause.setText(\"暂停\");\n        } else if (state == MultiTaskDownloader.PAUSED) {\n            viewHolder.tvWaiting.setText(\"已暂停\");\n            viewHolder.btPause.setText(\"继续下载\");\n        } else if (state == MultiTaskDownloader.COMPLETED) {\n            viewHolder.tvWaiting.setText(\"已完成\");\n            viewHolder.btPause.setText(\"已完成\");\n        } else if (state == MultiTaskDownloader.FAIL) {\n            viewHolder.tvWaiting.setText(\"下载失败\");\n            viewHolder.btPause.setText(\"重新下载\");\n        } else if (state == MultiTaskDownloader.CANCEL) {\n            viewHolder.tvWaiting.setText(\"已取消\");\n            viewHolder.btPause.setText(\"继续下载\");\n        }\n    }\n\n    private String getSpeed(long speed) {\n        float kb = speed * 1.0f / 1024;\n        if (kb > 1000) {\n            return String.format(Locale.getDefault(), \"%.2fMB/s\", kb / 1024);\n        } else {\n            return ((int) kb) + \"KB/s\";\n        }\n    }\n\n    private String getRemainingTime(long time) {\n        if (time < 300) {\n            return time + \"秒\";\n        } else {\n            long minute = time / 60;\n            if (time % 60 > 0) minute++;\n            return minute + \"分\";\n        }\n    }\n\n    @Override\n    public int getItemCount() {\n        return mDownloadTasks.size();\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return mDownloadTasks.get(position).hashCode();\n    }\n\n    static class MyViewHolder extends RecyclerView.ViewHolder {\n\n        ProgressBar progressBar;\n        TextView tvProgress;\n        TextView tvSize;\n        Button btPause;\n        TextView tvWaiting;\n\n\n        MyViewHolder(@NonNull View itemView) {\n            super(itemView);\n            tvWaiting = itemView.findViewById(R.id.tv_waiting);\n            tvProgress = itemView.findViewById(R.id.tv_progress);\n            tvSize = itemView.findViewById(R.id.tv_size);\n            progressBar = itemView.findViewById(R.id.progress_bar);\n            btPause = itemView.findViewById(R.id.bt_pause);\n        }\n    }\n\n\n    public void setOnItemClickListener(OnItemClickListener<DownloadTask> onItemClickListener) {\n        mOnItemClickListener = onItemClickListener;\n    }\n\n    public interface OnItemClickListener<T> {\n        void onItemClick(View view, T data, int position);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/ExceptionHelper.java",
    "content": "package com.example.httpsender;\n\n\nimport android.content.Context;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\n\n/**\n * 异常处理帮助类\n * User: ljx\n * Date: 2019/04/29\n * Time: 11:15\n */\npublic class ExceptionHelper {\n\n    @SuppressWarnings(\"deprecation\")\n    public static boolean isNetworkConnected(Context context) {\n        if (context != null) {\n            ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);\n            NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();\n            if (mNetworkInfo != null) {\n                return mNetworkInfo.isAvailable();\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/LoggingEventListener.kt",
    "content": "package com.example.httpsender\n\nimport android.util.Log\nimport okhttp3.Call\nimport okhttp3.Connection\nimport okhttp3.EventListener\nimport okhttp3.Handshake\nimport okhttp3.HttpUrl\nimport okhttp3.Protocol\nimport okhttp3.Request\nimport okhttp3.Response\nimport java.io.IOException\nimport java.net.InetAddress\nimport java.net.InetSocketAddress\nimport java.net.Proxy\nimport java.util.concurrent.TimeUnit\n\n/**\n * User: ljx\n * Date: 2022/4/22\n * Time: 16:05\n */\nclass LoggingEventListener : EventListener() {\n    private var startNs: Long = 0\n\n    override fun callStart(call: Call) {\n        startNs = System.nanoTime()\n\n        logWithTime(\"callStart: ${call.request()}\")\n    }\n\n    override fun proxySelectStart(call: Call, url: HttpUrl) {\n        logWithTime(\"proxySelectStart: $url\")\n    }\n\n    override fun proxySelectEnd(call: Call, url: HttpUrl, proxies: List<Proxy>) {\n        logWithTime(\"proxySelectEnd: $proxies\")\n    }\n\n    override fun dnsStart(call: Call, domainName: String) {\n        logWithTime(\"dnsStart: $domainName\")\n    }\n\n    override fun dnsEnd(call: Call, domainName: String, inetAddressList: List<InetAddress>) {\n        logWithTime(\"dnsEnd: $inetAddressList\")\n    }\n\n    override fun connectStart(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy) {\n        logWithTime(\"connectStart: $inetSocketAddress $proxy\")\n    }\n\n    override fun secureConnectStart(call: Call) {\n        logWithTime(\"secureConnectStart\")\n    }\n\n    override fun secureConnectEnd(call: Call, handshake: Handshake?) {\n        logWithTime(\"secureConnectEnd: $handshake\")\n    }\n\n    override fun connectEnd(\n        call: Call,\n        inetSocketAddress: InetSocketAddress,\n        proxy: Proxy,\n        protocol: Protocol?\n    ) {\n        logWithTime(\"connectEnd: $protocol\")\n    }\n\n    override fun connectFailed(\n        call: Call,\n        inetSocketAddress: InetSocketAddress,\n        proxy: Proxy,\n        protocol: Protocol?,\n        ioe: IOException\n    ) {\n        logWithTime(\"connectFailed: $protocol $ioe\")\n    }\n\n    override fun connectionAcquired(call: Call, connection: Connection) {\n        logWithTime(\"connectionAcquired: $connection\")\n    }\n\n    override fun connectionReleased(call: Call, connection: Connection) {\n        logWithTime(\"connectionReleased\")\n    }\n\n    override fun requestHeadersStart(call: Call) {\n        logWithTime(\"requestHeadersStart\")\n    }\n\n    override fun requestHeadersEnd(call: Call, request: Request) {\n        logWithTime(\"requestHeadersEnd\")\n    }\n\n    override fun requestBodyStart(call: Call) {\n        logWithTime(\"requestBodyStart\")\n    }\n\n    override fun requestBodyEnd(call: Call, byteCount: Long) {\n        logWithTime(\"requestBodyEnd: byteCount=$byteCount\")\n    }\n\n    override fun requestFailed(call: Call, ioe: IOException) {\n        logWithTime(\"requestFailed: $ioe\")\n    }\n\n    override fun responseHeadersStart(call: Call) {\n        logWithTime(\"responseHeadersStart\")\n    }\n\n    override fun responseHeadersEnd(call: Call, response: Response) {\n        logWithTime(\"responseHeadersEnd: $response\")\n    }\n\n    override fun responseBodyStart(call: Call) {\n        logWithTime(\"responseBodyStart\")\n    }\n\n    override fun responseBodyEnd(call: Call, byteCount: Long) {\n        logWithTime(\"responseBodyEnd: byteCount=$byteCount\")\n    }\n\n    override fun responseFailed(call: Call, ioe: IOException) {\n        logWithTime(\"responseFailed: $ioe\")\n    }\n\n    override fun callEnd(call: Call) {\n        logWithTime(\"callEnd\")\n    }\n\n    override fun callFailed(call: Call, ioe: IOException) {\n        logWithTime(\"callFailed: $ioe\")\n    }\n\n    override fun canceled(call: Call) {\n        logWithTime(\"canceled\")\n    }\n\n    override fun satisfactionFailure(call: Call, response: Response) {\n        logWithTime(\"satisfactionFailure: $response\")\n    }\n\n    override fun cacheHit(call: Call, response: Response) {\n        logWithTime(\"cacheHit: $response\")\n    }\n\n    override fun cacheMiss(call: Call) {\n        logWithTime(\"cacheMiss\")\n    }\n\n    override fun cacheConditionalHit(call: Call, cachedResponse: Response) {\n        logWithTime(\"cacheConditionalHit: $cachedResponse\")\n    }\n\n    private fun logWithTime(message: String) {\n        val timeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)\n        Log.e(\"LJX\", \"[$timeMs ms] $message\")\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/MainActivity.java",
    "content": "package com.example.httpsender;\n\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.ViewGroup.LayoutParams;\nimport android.view.animation.AccelerateInterpolator;\nimport android.view.animation.DecelerateInterpolator;\n\nimport androidx.activity.EdgeToEdge;\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.graphics.Insets;\nimport androidx.core.view.OnApplyWindowInsetsListener;\nimport androidx.core.view.ViewCompat;\nimport androidx.core.view.WindowInsetsCompat;\nimport androidx.databinding.DataBindingUtil;\nimport androidx.fragment.app.Fragment;\n\nimport com.example.httpsender.adapter.FragmentPageAdapter;\nimport com.example.httpsender.databinding.MainActivityBinding;\nimport com.example.httpsender.fragment.AwaitFragment;\nimport com.example.httpsender.fragment.FlowFragment;\nimport com.example.httpsender.fragment.MultiDownloadFragment;\nimport com.example.httpsender.fragment.RxJavaFragment;\nimport com.example.httpsender.view.ScaleTransitionPagerTitleView;\n\nimport net.lucode.hackware.magicindicator.MagicIndicator;\nimport net.lucode.hackware.magicindicator.ViewPagerHelper;\nimport net.lucode.hackware.magicindicator.buildins.UIUtil;\nimport net.lucode.hackware.magicindicator.buildins.commonnavigator.CommonNavigator;\nimport net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.CommonNavigatorAdapter;\nimport net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerIndicator;\nimport net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerTitleView;\nimport net.lucode.hackware.magicindicator.buildins.commonnavigator.indicators.LinePagerIndicator;\nimport net.lucode.hackware.magicindicator.buildins.commonnavigator.titles.SimplePagerTitleView;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class MainActivity extends AppCompatActivity {\n\n    private MainActivityBinding mBinding;\n\n    private final List<String> mDataList = new ArrayList<>();\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        EdgeToEdge.enable(this);\n        mBinding = DataBindingUtil.setContentView(this, R.layout.main_activity);\n        ViewCompat.setOnApplyWindowInsetsListener(mBinding.getRoot(), new OnApplyWindowInsetsListener() {\n            @NonNull\n            @Override\n            public WindowInsetsCompat onApplyWindowInsets(@NonNull View v, @NonNull WindowInsetsCompat insets) {\n                Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());\n                mBinding.magicIndicator.setPadding(0, systemBars.top, 0, 0);\n                LayoutParams layoutParams = mBinding.magicIndicator.getLayoutParams();\n                layoutParams.height = (int) (getResources().getDisplayMetrics().density * 50 + systemBars.top);\n                mBinding.magicIndicator.setLayoutParams(layoutParams);\n                return insets;\n            }\n        });\n        mBinding.viewPager.setOffscreenPageLimit(4);\n        mDataList.add(\"RxJava\");\n        mDataList.add(\"Await\");\n        mDataList.add(\"Flow\");\n        mDataList.add(\"Multi Download\");\n        List<Fragment> fragments = new ArrayList<>();\n        fragments.add(new RxJavaFragment());\n        fragments.add(new AwaitFragment());\n        fragments.add(new FlowFragment());\n        fragments.add(new MultiDownloadFragment());\n        mBinding.viewPager.setAdapter(new FragmentPageAdapter(getSupportFragmentManager(), fragments, mDataList));\n        initMagicIndicator();\n    }\n\n    public void initMagicIndicator() {\n        MagicIndicator magicIndicator = mBinding.magicIndicator;\n        CommonNavigator commonNavigator = new CommonNavigator(this);\n        commonNavigator.setAdapter(new CommonNavigatorAdapter() {\n            @Override\n            public int getCount() {\n                return mDataList == null ? 0 : mDataList.size();\n            }\n\n            @Override\n            public IPagerTitleView getTitleView(Context context, final int index) {\n                SimplePagerTitleView simplePagerTitleView = new ScaleTransitionPagerTitleView(context);\n                simplePagerTitleView.setPadding(dp2px(5), 0, 0, 0);\n                simplePagerTitleView.setText(mDataList.get(index));\n                simplePagerTitleView.setTextSize(18);\n                simplePagerTitleView.setNormalColor(Color.parseColor(\"#c8e6c9\"));\n                simplePagerTitleView.setSelectedColor(Color.WHITE);\n                simplePagerTitleView.setOnClickListener(v -> mBinding.viewPager.setCurrentItem(index));\n                return simplePagerTitleView;\n            }\n\n            @Override\n            public IPagerIndicator getIndicator(Context context) {\n                LinePagerIndicator indicator = new LinePagerIndicator(context);\n                indicator.setMode(LinePagerIndicator.MODE_EXACTLY);\n                indicator.setLineHeight(UIUtil.dip2px(context, 3));\n                indicator.setLineWidth(UIUtil.dip2px(context, 30));\n                indicator.setRoundRadius(UIUtil.dip2px(context, 3));\n                indicator.setStartInterpolator(new AccelerateInterpolator());\n                indicator.setYOffset(dp2px(3));\n                indicator.setEndInterpolator(new DecelerateInterpolator(2.0f));\n                indicator.setColors(Color.WHITE);\n                return indicator;\n            }\n        });\n        magicIndicator.setNavigator(commonNavigator);\n        ViewPagerHelper.bind(magicIndicator, mBinding.viewPager);\n    }\n\n    private int dp2px(double dpValue) {\n        float density = getResources().getDisplayMetrics().density;\n        return (int) (dpValue * density + 0.5);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/MyViewModel.kt",
    "content": "package com.example.httpsender\n\nimport android.app.Application\nimport android.util.Log\nimport androidx.lifecycle.viewModelScope\nimport com.example.httpsender.entity.Article\nimport com.example.httpsender.entity.PageList\nimport com.rxjava.rxlife.ScopeViewModel\nimport com.rxjava.rxlife.lifeOnMain\nimport io.reactivex.rxjava3.core.Observable\nimport kotlinx.coroutines.TimeoutCancellationException\nimport kotlinx.coroutines.launch\nimport rxhttp.awaitResult\nimport rxhttp.retry\nimport rxhttp.timeout\nimport rxhttp.wrapper.param.RxHttp\nimport rxhttp.wrapper.param.toAwaitResponse\nimport java.util.concurrent.TimeUnit\n\n/**\n * User: ljx\n * Date: 2019-05-31\n * Time: 21:50\n */\nclass MyViewModel(application: Application) : ScopeViewModel(application) {\n\n    fun startInterval() {\n        Observable.interval(1, 1, TimeUnit.SECONDS)\n            .lifeOnMain(this)\n            .subscribe { Log.e(\"LJX\", \"MyViewModel aLong=$it\") }\n    }\n\n    fun testRetry() = viewModelScope.launch {\n        RxHttp.get(\"/article/list/0/json\")\n            .toAwaitResponse<PageList<Article>>()\n            .timeout(100)\n            .retry(2, 1000) {\n                it is TimeoutCancellationException\n            }\n            .awaitResult {\n                Log.e(\"LJX\", \"pageList=$it\")\n            }\n            .onFailure {\n                Log.e(\"LJX\", \"it=$it\")\n            }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/OnError.java",
    "content": "package com.example.httpsender;\n\n\nimport com.example.httpsender.entity.ErrorInfo;\n\nimport io.reactivex.rxjava3.functions.Consumer;\n\n\n/**\n * RxJava 错误回调 ,加入网络异常处理\n * User: ljx\n * Date: 2019/04/29\n * Time: 11:15\n */\npublic interface OnError extends Consumer<Throwable> {\n\n    @Override\n    default void accept(Throwable throwable) throws Exception {\n        onError(new ErrorInfo(throwable));\n    }\n\n    void onError(ErrorInfo error) throws Exception;\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/Presenter.kt",
    "content": "package com.example.httpsender\n\nimport android.util.Log\nimport androidx.lifecycle.LifecycleOwner\nimport com.example.httpsender.entity.Article\nimport com.example.httpsender.entity.PageList\nimport com.rxjava.rxlife.BaseScope\nimport com.rxjava.rxlife.life\nimport io.reactivex.rxjava3.core.Observable\nimport kotlinx.coroutines.*\nimport rxhttp.*\nimport rxhttp.wrapper.cache.CacheMode\nimport rxhttp.wrapper.param.RxHttp\nimport rxhttp.wrapper.param.toAwaitResponse\nimport java.util.concurrent.TimeUnit\n\n/**\n * User: ljx\n * Date: 2019-05-26\n * Time: 15:20\n */\nclass Presenter(owner: LifecycleOwner) : BaseScope(owner) {\n\n    fun test() {\n        Observable.interval(1, 1, TimeUnit.SECONDS)\n            .life(this) //这里的this 为Scope接口对象\n            .subscribe { Log.e(\"LJX\", \"accept aLong=$it\") }\n    }\n\n    fun testRetry() {\n        val coroutineScope: CoroutineScope =\n            CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)\n        coroutineScope.launch {\n            RxHttp.postForm(\"/article/query/0/json\")\n                .add(\"k\", \"性能优化\")\n                .setCacheMode(CacheMode.ONLY_NETWORK)\n                .toAwaitResponse<PageList<Article>>()\n                .delay(100)\n                .startDelay(100)\n                .onErrorReturnItem(PageList())\n                .timeout(1000)\n                .retry(2, 1000) {\n                    it is TimeoutCancellationException\n                }\n                .awaitResult {\n                    Log.e(\"RxHttp\", \"onNext = $it\")\n                }.onFailure {\n                    Log.e(\"RxHttp\", \"onError = $it\")\n                }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/RxHttpManager.java",
    "content": "package com.example.httpsender;\n\n\nimport android.app.Application;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\n\nimport okhttp3.OkHttpClient;\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.wrapper.annotation.Converter;\nimport rxhttp.wrapper.annotation.OkClient;\nimport rxhttp.wrapper.callback.IConverter;\nimport rxhttp.wrapper.converter.FastJsonConverter;\nimport rxhttp.wrapper.converter.XmlConverter;\nimport rxhttp.wrapper.cookie.CookieStore;\nimport rxhttp.wrapper.param.Method;\nimport rxhttp.wrapper.ssl.HttpsUtils;\nimport rxhttp.wrapper.ssl.HttpsUtils.SSLParams;\n\n/**\n * 本类所有配置都是非必须的，根据自己需求选择就好\n * User: ljx\n * Date: 2019-11-26\n * Time: 20:44\n */\npublic class RxHttpManager {\n\n    @Converter(name = \"XmlConverter\")  //非必须\n    public static IConverter xmlConverter = XmlConverter.create();\n    @Converter(name = \"FastJsonConverter\")  //非必须\n    public static IConverter fastJsonConverter = FastJsonConverter.create();\n\n    @OkClient(name = \"SimpleClient\", className = \"Simple\")  //非必须\n    public static OkHttpClient simpleClient = new OkHttpClient.Builder().build();\n\n\n    public static void init(Application context) {\n        File file = new File(context.getExternalCacheDir(), \"RxHttpCookie\");\n        SSLParams sslParams = HttpsUtils.getSslSocketFactory();\n        OkHttpClient client = new OkHttpClient.Builder()\n            .cookieJar(new CookieStore(file))\n            .connectTimeout(10, TimeUnit.SECONDS)\n            .readTimeout(10, TimeUnit.SECONDS)\n            .writeTimeout(10, TimeUnit.SECONDS)\n            .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager) //添加信任证书\n            .hostnameVerifier((hostname, session) -> true) //忽略host验证\n//            .followRedirects(false)  //禁制OkHttp的重定向操作，我们自己处理重定向\n//            .addInterceptor(new RedirectInterceptor())\n//            .addInterceptor(new TokenInterceptor())\n            .build();\n\n        //设置缓存策略，非必须\n//        File cacheFile = new File(context.getExternalCacheDir(), \"RxHttpCache\");\n        //RxHttp初始化，非必须\n        RxHttpPlugins.init(client)                     //自定义OkHttpClient对象\n            .setDebug(BuildConfig.DEBUG, false, 2)      //调试模式/分段打印/json数据缩进空间\n//            .setCache(cacheFile, 1000 * 100, CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE)\n//            .setExcludeCacheKeys(\"time\")               //设置一些key，不参与cacheKey的组拼\n//            .setResultDecoder(s -> s)                  //设置数据解密/解码器，非必须\n//            .setConverter(FastJsonConverter.create())  //设置全局的转换器，非必须\n            .setOnParamAssembly(p -> {                 //设置公共参数，非必须\n                //1、可根据不同请求添加不同参数，每次发送请求前都会被回调\n                //2、如果希望部分请求不回调这里，发请求前调用RxHttp#setAssemblyEnabled(false)即可\n                Method method = p.getMethod();\n                if (method.isGet()) {\n                    p.add(\"method\", \"get\");\n                } else if (method.isPost()) { //Post请求\n                    p.add(\"method\", \"post\");\n                }\n                p.add(\"versionName\", \"1.0.0\")//添加公共参数\n                    .add(\"time\", System.currentTimeMillis())\n                    .addHeader(\"deviceType\", \"android\"); //添加公共请求头\n            });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/Tip.java",
    "content": "package com.example.httpsender;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.widget.Toast;\n\n\n/**\n * 可在任意线程执行本类方法\n * User: ljx\n * Date: 2017/3/8\n * Time: 10:31\n */\npublic class Tip {\n\n    private static Handler mHandler = new Handler(Looper.getMainLooper());\n    private static Toast mToast;\n\n    public static void show(int msgResId) {\n        show(msgResId, false);\n    }\n\n    public static void show(int msgResId, boolean timeLong) {\n        show(AppHolder.getInstance().getString(msgResId), timeLong);\n    }\n\n    public static void show(CharSequence msg) {\n        show(msg, false);\n    }\n\n    public static void show(final CharSequence msg, final boolean timeLong) {\n        runOnUiThread(() -> {\n            if (mToast != null) {\n                mToast.cancel();\n            }\n            int duration = timeLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT;\n            mToast = Toast.makeText(AppHolder.getInstance(), msg, duration);\n            mToast.show();\n        });\n    }\n\n    private static void runOnUiThread(Runnable runnable) {\n        if (Looper.getMainLooper() == Looper.myLooper()) {\n            runnable.run();\n        } else {\n            mHandler.post(runnable);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/ToolBarActivity.java",
    "content": "package com.example.httpsender;\n\nimport android.os.Build;\nimport android.view.MenuItem;\nimport android.view.ViewGroup;\n\nimport androidx.annotation.LayoutRes;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.appcompat.widget.Toolbar;\nimport androidx.databinding.DataBindingUtil;\nimport androidx.databinding.ViewDataBinding;\n\n\n/**\n * 需要ToolBar的activity都必须继承本类\n */\npublic abstract class ToolBarActivity extends AppCompatActivity {\n\n    protected Toolbar toolbar;\n    protected ActionBar actionBar;\n    private ViewGroup mContainer;\n\n    public <T extends ViewDataBinding> T bindingInflate(@LayoutRes int layoutResID) {\n        initView();\n        return DataBindingUtil.inflate(getLayoutInflater(), layoutResID, mContainer, true);\n    }\n\n    @Override\n    public void setContentView(@LayoutRes int layoutResID) {\n        initView();\n        getLayoutInflater().inflate(layoutResID, mContainer, true);\n    }\n\n    private void initView() {\n        super.setContentView(R.layout.toolbar_activity);\n        mContainer = findViewById(R.id.container);\n        toolbar = findViewById(R.id.toolbar);\n        setSupportActionBar(toolbar);\n        actionBar = getSupportActionBar();\n        if (actionBar != null) {\n            actionBar.setDisplayHomeAsUpEnabled(true);\n        }\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        int itemId = item.getItemId();\n        if (itemId == android.R.id.home) {\n            boolean popSuccess = getSupportFragmentManager().popBackStackImmediate();\n            if (popSuccess) return true;\n            finish();\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    public void setToolbarColor(int color) {\n        toolbar.setBackgroundColor(color);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            getWindow().setStatusBarColor(color);\n        }\n    }\n\n    @Override\n    public void setTitle(CharSequence title) {\n        super.setTitle(title);\n        if (toolbar == null) return;\n        toolbar.setTitle(title);\n    }\n\n    @Override\n    public void setTitle(int titleId) {\n        super.setTitle(titleId);\n        if (toolbar == null) return;\n        toolbar.setTitle(titleId);\n    }\n\n    protected int dp2px(float dpValue) {\n        final float scale = getResources().getDisplayMetrics().density;\n        return (int) (dpValue * scale + 0.5f);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/adapter/FragmentPageAdapter.java",
    "content": "package com.example.httpsender.adapter;\n\n\n\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.fragment.app.FragmentPagerAdapter;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n\n/**\n * User: ljx\n * Date: 2018/6/9\n * Time: 13:53\n */\npublic class FragmentPageAdapter extends FragmentPagerAdapter {\n\n    private List<? extends Fragment> mFragments;\n    private List<? extends CharSequence> mTitles;\n\n\n    public FragmentPageAdapter(FragmentManager fm, List<? extends Fragment> fragments, String[] titles) {\n        this(fm, fragments, Arrays.asList(titles));\n    }\n\n    public FragmentPageAdapter(FragmentManager fm, List<? extends Fragment> fragments, List<? extends CharSequence> titles) {\n        super(fm);\n        mFragments = fragments;\n        mTitles = titles;\n    }\n\n    @Override\n    public int getCount() {\n        return mFragments.size();\n    }\n\n    @Override\n    public CharSequence getPageTitle(int position) {\n        return mTitles.get(position);\n    }\n\n    @Override\n    public Fragment getItem(int position) {\n        return mFragments.get(position);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/entity/Article.java",
    "content": "package com.example.httpsender.entity;\n\n/**\n * 文章实体类\n * User: ljx\n * Date: 2019-09-24\n * Time: 10:23\n */\npublic class Article {\n\n\n    /**\n     * apkLink :\n     * audit : 1\n     * author :\n     * chapterId : 76\n     * chapterName : 项目架构\n     * collect : false\n     * courseId : 13\n     * desc :\n     * envelopePic :\n     * fresh : true\n     * id : 9300\n     * link : https://mp.weixin.qq.com/s/_6p6vfce7m5E8AwGDg2cZg\n     * niceDate : 10小时前\n     * niceShareDate : 11小时前\n     * origin :\n     * prefix :\n     * projectLink :\n     * publishTime : 1569255115000\n     * shareDate : 1569249942000\n     * shareUser : ZYLAB\n     * superChapterId : 74\n     * superChapterName : 热门专题\n     * tags : []\n     * title : Android 开发中的架构模式 -- MVC / MVP / MVVM\n     * type : 0\n     * userId : 10577\n     * visible : 1\n     * zan : 0\n     */\n\n    private String apkLink;\n    private int     audit;\n    private String  author;\n    private int     chapterId;\n    private String  chapterName;\n    private boolean collect;\n    private int     courseId;\n    private String  desc;\n    private String  envelopePic;\n    private boolean fresh;\n    private int     id;\n    private String  link;\n    private String  niceDate;\n    private String  niceShareDate;\n    private String  origin;\n    private String  prefix;\n    private String  projectLink;\n    private long    publishTime;\n    private long    shareDate;\n    private String  shareUser;\n    private int     superChapterId;\n    private String  superChapterName;\n    private String  title;\n    private int     type;\n    private int     userId;\n    private int     visible;\n    private int     zan;\n\n    public String getApkLink() {\n        return apkLink;\n    }\n\n    public int getAudit() {\n        return audit;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public int getChapterId() {\n        return chapterId;\n    }\n\n    public String getChapterName() {\n        return chapterName;\n    }\n\n    public boolean isCollect() {\n        return collect;\n    }\n\n    public int getCourseId() {\n        return courseId;\n    }\n\n    public String getDesc() {\n        return desc;\n    }\n\n    public String getEnvelopePic() {\n        return envelopePic;\n    }\n\n    public boolean isFresh() {\n        return fresh;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public String getLink() {\n        return link;\n    }\n\n    public String getNiceDate() {\n        return niceDate;\n    }\n\n    public String getNiceShareDate() {\n        return niceShareDate;\n    }\n\n    public String getOrigin() {\n        return origin;\n    }\n\n    public String getPrefix() {\n        return prefix;\n    }\n\n    public String getProjectLink() {\n        return projectLink;\n    }\n\n    public long getPublishTime() {\n        return publishTime;\n    }\n\n    public long getShareDate() {\n        return shareDate;\n    }\n\n    public String getShareUser() {\n        return shareUser;\n    }\n\n    public int getSuperChapterId() {\n        return superChapterId;\n    }\n\n    public String getSuperChapterName() {\n        return superChapterName;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public int getType() {\n        return type;\n    }\n\n    public int getUserId() {\n        return userId;\n    }\n\n    public int getVisible() {\n        return visible;\n    }\n\n    public int getZan() {\n        return zan;\n    }\n\n\n    public void setApkLink(String apkLink) {\n        this.apkLink = apkLink;\n    }\n\n    public void setAudit(int audit) {\n        this.audit = audit;\n    }\n\n    public void setAuthor(String author) {\n        this.author = author;\n    }\n\n    public void setChapterId(int chapterId) {\n        this.chapterId = chapterId;\n    }\n\n    public void setChapterName(String chapterName) {\n        this.chapterName = chapterName;\n    }\n\n    public void setCollect(boolean collect) {\n        this.collect = collect;\n    }\n\n    public void setCourseId(int courseId) {\n        this.courseId = courseId;\n    }\n\n    public void setDesc(String desc) {\n        this.desc = desc;\n    }\n\n    public void setEnvelopePic(String envelopePic) {\n        this.envelopePic = envelopePic;\n    }\n\n    public void setFresh(boolean fresh) {\n        this.fresh = fresh;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public void setLink(String link) {\n        this.link = link;\n    }\n\n    public void setNiceDate(String niceDate) {\n        this.niceDate = niceDate;\n    }\n\n    public void setNiceShareDate(String niceShareDate) {\n        this.niceShareDate = niceShareDate;\n    }\n\n    public void setOrigin(String origin) {\n        this.origin = origin;\n    }\n\n    public void setPrefix(String prefix) {\n        this.prefix = prefix;\n    }\n\n    public void setProjectLink(String projectLink) {\n        this.projectLink = projectLink;\n    }\n\n    public void setPublishTime(long publishTime) {\n        this.publishTime = publishTime;\n    }\n\n    public void setShareDate(long shareDate) {\n        this.shareDate = shareDate;\n    }\n\n    public void setShareUser(String shareUser) {\n        this.shareUser = shareUser;\n    }\n\n    public void setSuperChapterId(int superChapterId) {\n        this.superChapterId = superChapterId;\n    }\n\n    public void setSuperChapterName(String superChapterName) {\n        this.superChapterName = superChapterName;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    public void setType(int type) {\n        this.type = type;\n    }\n\n    public void setUserId(int userId) {\n        this.userId = userId;\n    }\n\n    public void setVisible(int visible) {\n        this.visible = visible;\n    }\n\n    public void setZan(int zan) {\n        this.zan = zan;\n    }\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        Article article = (Article) o;\n\n        return id == article.id;\n    }\n\n    @Override\n    public int hashCode() {\n        return id;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/entity/DownloadTask.java",
    "content": "package com.example.httpsender.entity;\n\n\n\n/**\n * User: ljx\n * Date: 2019-06-08\n * Time: 10:09\n */\npublic class DownloadTask {\n\n    private String url;\n    private String localPath;\n\n    private float progress;\n    private long currentSize;\n    private long totalSize;\n\n    private long speed;\n    private long remainingTime;\n\n    private int state; //0=未开始 1=等待中 2=下载中 3=暂停中 4=已完成  5=下载失败 6=已取消\n\n\n    public DownloadTask(String url) {\n        this.url = url;\n    }\n\n    public int getState() {\n        return state;\n    }\n\n    public void setState(int state) {\n        this.state = state;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public void setUrl(String url) {\n        this.url = url;\n    }\n\n    public String getLocalPath() {\n        return localPath;\n    }\n\n    public void setLocalPath(String localPath) {\n        this.localPath = localPath;\n    }\n\n    public long getSpeed() {\n        return speed;\n    }\n\n    public void setSpeed(long speed) {\n        this.speed = speed;\n    }\n\n    public long getRemainingTime() {\n        return remainingTime;\n    }\n\n    public void setRemainingTime(long remainingTime) {\n        this.remainingTime = remainingTime;\n    }\n\n    public float getProgress() {\n        return progress;\n    }\n\n    public void setProgress(float progress) {\n        this.progress = progress;\n    }\n\n    public long getCurrentSize() {\n        return currentSize;\n    }\n\n    public void setCurrentSize(long currentSize) {\n        this.currentSize = currentSize;\n    }\n\n    public long getTotalSize() {\n        return totalSize;\n    }\n\n    public void setTotalSize(long totalSize) {\n        this.totalSize = totalSize;\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        DownloadTask task = (DownloadTask) o;\n\n        return url.equals(task.url);\n    }\n\n    @Override\n    public int hashCode() {\n        return url.hashCode();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/entity/ErrorInfo.java",
    "content": "package com.example.httpsender.entity;\n\nimport android.text.TextUtils;\n\nimport com.example.httpsender.AppHolder;\nimport com.example.httpsender.ExceptionHelper;\nimport com.example.httpsender.R;\nimport com.example.httpsender.Tip;\nimport com.google.gson.JsonSyntaxException;\n\nimport java.net.ConnectException;\nimport java.net.SocketTimeoutException;\nimport java.net.UnknownHostException;\nimport java.util.concurrent.TimeoutException;\n\nimport rxhttp.wrapper.exception.HttpStatusCodeException;\nimport rxhttp.wrapper.exception.ParseException;\n\n/**\n * Http请求错误信息\n * User: ljx\n * Date: 2019-06-26\n * Time: 14:26\n */\npublic class ErrorInfo {\n\n    private int errorCode;  //仅指服务器返回的错误码\n    private String errorMsg; //错误文案，网络错误、请求失败错误、服务器返回的错误等文案\n    private Throwable throwable; //异常信息\n\n    public ErrorInfo(Throwable throwable) {\n        this.throwable = throwable;\n        String errorMsg = null;\n        if (throwable instanceof UnknownHostException) {\n            if (!ExceptionHelper.isNetworkConnected(AppHolder.getInstance())) {\n                errorMsg = getString(R.string.network_error);\n            } else {\n                errorMsg = getString(R.string.notify_no_network);\n            }\n        } else if (throwable instanceof SocketTimeoutException || throwable instanceof TimeoutException) {\n            //前者是通过OkHttpClient设置的超时引发的异常，后者是对单个请求调用timeout方法引发的超时异常\n            errorMsg = getString(R.string.time_out_please_try_again_later);\n        } else if (throwable instanceof ConnectException) {\n            errorMsg = getString(R.string.esky_service_exception);\n        } else if (throwable instanceof HttpStatusCodeException) { //请求失败异常\n            String code = throwable.getLocalizedMessage();\n            if (\"416\".equals(code)) {\n                errorMsg = \"请求范围不符合要求\";\n            } else {\n                errorMsg = throwable.getMessage();\n            }\n        } else if (throwable instanceof JsonSyntaxException) { //请求成功，但Json语法异常,导致解析失败\n            errorMsg = \"数据解析失败,请稍后再试\";\n        } else if (throwable instanceof ParseException) { // ParseException异常表明请求成功，但是数据不正确\n            String errorCode = throwable.getLocalizedMessage();\n            this.errorCode = Integer.parseInt(errorCode);\n            errorMsg = throwable.getMessage();\n            if (TextUtils.isEmpty(errorMsg)) errorMsg = errorCode;//errorMsg为空，显示errorCode\n        } else {\n            errorMsg = throwable.getMessage();\n        }\n        this.errorMsg = errorMsg;\n    }\n\n    public int getErrorCode() {\n        return errorCode;\n    }\n\n    public String getErrorMsg() {\n        return errorMsg;\n    }\n\n    public Throwable getThrowable() {\n        return throwable;\n    }\n\n    public boolean show() {\n        Tip.show(TextUtils.isEmpty(errorMsg) ? throwable.getMessage() : errorMsg);\n        return true;\n    }\n\n    /**\n     * @param standbyMsg 备用的提示文案\n     */\n    public boolean show(String standbyMsg) {\n        Tip.show(TextUtils.isEmpty(errorMsg) ? standbyMsg : errorMsg);\n        return true;\n    }\n\n    /**\n     * @param standbyMsg 备用的提示文案\n     */\n    public boolean show(int standbyMsg) {\n        Tip.show(TextUtils.isEmpty(errorMsg) ? AppHolder.getInstance().getString(standbyMsg) : errorMsg);\n        return true;\n    }\n\n    public String getString(int resId) {\n        return AppHolder.getInstance().getString(resId);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/entity/Location.java",
    "content": "package com.example.httpsender.entity;\n\n/**\n * User: ljx\n * Date: 2019-11-18\n * Time: 14:51\n */\npublic class Location {\n\n    private double longitude;\n    private double latitude;\n\n    public Location(double longitude, double latitude) {\n        this.longitude = longitude;\n        this.latitude = latitude;\n    }\n\n    public double getLongitude() {\n        return longitude;\n    }\n\n    public double getLatitude() {\n        return latitude;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/entity/Name.java",
    "content": "package com.example.httpsender.entity;\n\n/**\n * User: ljx\n * Date: 2019-11-18\n * Time: 16:19\n */\npublic class Name {\n\n    String name;\n\n    public Name(String name) {\n        this.name = name;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/entity/NewsDataXml.java",
    "content": "package com.example.httpsender.entity;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.ElementList;\nimport org.simpleframework.xml.Root;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * User: ljx\n * Date: 2019-11-22\n * Time: 23:34\n */\n@Root(name = \"body\", strict = false) //name:要解析的xml数据的头部\npublic class NewsDataXml {\n    @Attribute\n    public String copyright; //属性\n    @ElementList(required = true, inline = true, entry = \"route\") //标志是集合\n    public List<NewsXml> newsXmls = new ArrayList<>();\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/entity/NewsXml.java",
    "content": "package com.example.httpsender.entity;\n\nimport org.simpleframework.xml.Attribute;\nimport org.simpleframework.xml.Root;\n\n/**\n * User: ljx\n * Date: 2019-11-22\n * Time: 23:34\n */\n@Root(name = \"route\", strict = false) //要解析的xml数据的头部\npublic class NewsXml {\n    @Attribute\n    public String tag;\n    @Attribute\n    public String title;\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/entity/PageList.java",
    "content": "package com.example.httpsender.entity;\n\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * User: ljx\n * Date: 2018/10/21\n * Time: 13:16\n */\npublic class PageList<T> {\n\n    private int     curPage; //当前页数\n    private int     pageCount; //总页数\n    private int     total; //总条数\n    private List<T> datas;\n\n    public int getCurPage() {\n        return curPage;\n    }\n\n    public int getPageCount() {\n        return pageCount;\n    }\n\n    public int getTotal() {\n        return total;\n    }\n\n    public List<T> getDatas() {\n        return datas;\n    }\n\n    public void setCurPage(int curPage) {\n        this.curPage = curPage;\n    }\n\n    public void setPageCount(int pageCount) {\n        this.pageCount = pageCount;\n    }\n\n    public void setTotal(int total) {\n        this.total = total;\n    }\n\n    public void setDatas(ArrayList<T> datas) {\n        this.datas = datas;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/entity/Response.java",
    "content": "package com.example.httpsender.entity;\n\n\n/**\n * User: ljx\n * Date: 2018/10/21\n * Time: 13:16\n */\npublic class Response<T> {\n\n    private int    errorCode;\n    private String errorMsg;\n    private T      data;\n\n    public int getCode() {\n        return errorCode;\n    }\n\n    public String getMsg() {\n        return errorMsg;\n    }\n\n    public T getData() {\n        return data;\n    }\n\n    public void setErrorCode(int errorCode) {\n        this.errorCode = errorCode;\n    }\n\n    public void setErrorMsg(String errorMsg) {\n        this.errorMsg = errorMsg;\n    }\n\n    public void setData(T data) {\n        this.data = data;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/entity/Url.kt",
    "content": "package com.example.httpsender.entity\n\nimport rxhttp.wrapper.annotation.DefaultDomain\nimport rxhttp.wrapper.annotation.Domain\n\n/**\n * User: ljx\n * Date: 2020/2/27\n * Time: 23:55\n */\nobject Url {\n\n    @JvmField\n    @DefaultDomain //设置为默认域名\n    var baseUrl = \"https://www.wanandroid.com/\"\n\n    const val UPLOAD_URL = \"http://t.xinhuo.com/index.php/Api/Pic/uploadPic\"\n\n    const val DOWNLOAD_URL = \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk\"\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/entity/User.java",
    "content": "package com.example.httpsender.entity;\n\n/**\n * User: ljx\n * Date: 2019-12-04\n * Time: 12:13\n */\npublic class User {\n\n    private static User mUser;\n\n    private String token;\n\n    public static User get() {\n        if (mUser == null) {\n            synchronized (User.class) {\n                if (mUser == null)\n                    mUser = new User();\n            }\n        }\n        return mUser;\n    }\n\n    public String getToken() {\n        return token;\n    }\n\n    public void setToken(String token) {\n        this.token = token;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/fragment/AwaitFragment.kt",
    "content": "package com.example.httpsender.fragment\n\nimport android.graphics.Color\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.lifecycle.lifecycleScope\nimport com.example.httpsender.R\nimport com.example.httpsender.databinding.AwaitFragmentBinding\nimport com.example.httpsender.entity.*\nimport com.example.httpsender.kt.errorMsg\nimport com.example.httpsender.kt.show\nimport com.google.gson.Gson\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.launch\nimport rxhttp.*\nimport rxhttp.wrapper.param.RxHttp\nimport rxhttp.wrapper.param.toAwaitResponse\nimport java.io.File\nimport java.util.*\n\n/**\n * 使用 协程(RxHttp + Await) 发请求\n *\n * ```\n * val user = RxHttp.postXxx(\"/service/...\")\n *     .add(\"key\", \"value\")\n *     .toAwait<User>()\n *     .awaitResult {\n *         val user = it\n *     }.onFailure {\n *         val throwable = it\n *     }\n *```\n *\n * User: ljx\n * Date: 2020/4/24\n * Time: 18:16\n */\nclass AwaitFragment : BaseFragment<AwaitFragmentBinding>(), View.OnClickListener {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.await_fragment)\n    }\n\n    override fun AwaitFragmentBinding.onViewCreated(savedInstanceState: Bundle?) {\n        click = this@AwaitFragment\n    }\n\n    //发送Get请求，获取文章列表\n    private suspend fun AwaitFragmentBinding.sendGet(view: View) {\n        RxHttp.get(\"/article/list/0/json\")\n            .toAwaitResponse<PageList<Article>>()\n            .toAwaitOkResponse()\n            .awaitResult {\n                val list = it.body()\n                val response = it.raw()\n                val headers = it.headers()\n                tvResult.text = Gson().toJson(list)\n            }.onFailure {\n                tvResult.text = it.errorMsg\n                //失败回调\n                it.show()\n            }\n    }\n\n    //发送Post表单请求,根据关键字查询文章\n    private suspend fun AwaitFragmentBinding.sendPostForm(view: View) {\n        RxHttp.postForm(\"/article/query/0/json\")\n            .add(\"k\", \"性能优化\")\n            .toAwaitResponse<PageList<Article>>()\n            .awaitResult {\n                tvResult.text = Gson().toJson(it)\n            }.onFailure {\n                tvResult.text = it.errorMsg\n                //失败回调\n                it.show()\n            }\n    }\n\n    //发送Post Json请求，此接口不通，通过日志可以看到，发送出去的json对象\n    private suspend fun AwaitFragmentBinding.sendPostJson(view: View) {\n        /*\n           发送以下User对象\n           {\"name\":\"张三\",\"sex\":1,\"height\":180,\"weight\":70,\n           \"interest\":[\"羽毛球\",\"游泳\"],\n           \"location\":{\"latitude\":30.7866,\"longitude\":120.6788},\n           \"address\":{\"street\":\"科技园路.\",\"city\":\"江苏苏州\",\"country\":\"中国\"}}\n         */\n        val interestList: MutableList<String> = ArrayList() //爱好\n        interestList.add(\"羽毛球\")\n        interestList.add(\"游泳\")\n        val address = \"\"\"\n            {\"street\":\"科技园路.\",\"city\":\"江苏苏州\",\"country\":\"中国\"}\n        \"\"\".trimIndent()\n        RxHttp.postJson(\"/article/list/0/json\")\n            .add(\"name\", \"张三\")\n            .add(\"sex\", 1)\n            .addAll(\"\"\"{\"height\":180,\"weight\":70}\"\"\") //通过addAll系列方法添加多个参数\n            .add(\"interest\", interestList) //添加数组对象\n            .add(\"location\", Location(120.6788, 30.7866)) //添加位置对象\n            .addJsonElement(\"address\", address) //通过字符串添加一个对象\n            .toAwaitString()\n            .awaitResult {\n                tvResult.text = it\n            }.onFailure {\n                tvResult.text = it.errorMsg\n                //失败回调\n                it.show()\n            }\n    }\n\n    //发送Post JsonArray请求，通过日志可以看到，发送出去的json数组\n    private suspend fun AwaitFragmentBinding.sendPostJsonArray(view: View) {\n        /*\n           发送以下Json数组\n           [{\"name\":\"张三\"},{\"name\":\"李四\"},{\"name\":\"王五\"},{\"name\":\"赵六\"},{\"name\":\"杨七\"}]\n         */\n        val names: MutableList<Name?> = ArrayList()\n        names.add(Name(\"赵六\"))\n        names.add(Name(\"杨七\"))\n        RxHttp.postJsonArray(\"/article/list/0/json\")\n            .add(\"name\", \"张三\")\n            .add(Name(\"李四\"))\n            .addJsonElement(\"\"\"{\"name\":\"王五\"}\"\"\")\n            .addAll(names)\n            .toAwaitString()\n            .awaitResult {\n                tvResult.text = it\n            }.onFailure {\n                tvResult.text = it.errorMsg\n                //失败回调\n                it.show()\n            }\n\n    }\n\n    //此接口不同，但通过日志可以看到，发送出去的是xml数据，如果收到也是xml数据，则会自动解析为我们指定的对象\n    private suspend fun AwaitFragmentBinding.xmlConverter(view: View) {\n        RxHttp.postBody(\"http://webservices.nextbus.com/service/publicXMLFeed?command=routeConfig&a=sf-muni\")\n            .setBody(Name(\"张三\"))\n            .setXmlConverter()\n            .toAwait<NewsDataXml>()\n            .awaitResult {\n                tvResult.text = Gson().toJson(it)\n            }.onFailure {\n                tvResult.text = it.errorMsg\n                //失败回调\n                it.show()\n            }\n    }\n\n    /**\n     * android 10之前 或 沙盒目录(Android/data/packageName/)下的文件上传\n     *\n     * 注意：这里并非通过 [Await] 实现的， 而是通过 [Flow] 监听的进度，因为在监听上传进度这块，Flow性能更优，且更简单\n     *\n     * 如不需要监听进度，toFlow 方法不要传进度回调即可\n     */\n    private suspend fun AwaitFragmentBinding.upload(v: View) {\n        RxHttp.postForm(Url.UPLOAD_URL)\n            .addFile(\"file\", File(\"xxxx/1.png\"))\n            .toFlow<String>()\n            .onProgress {\n                //上传进度回调,0-100，仅在进度有更新时才会回调\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize  //当前已上传的字节大小\n                val totalSize = it.totalSize      //要上传的总字节大小\n                tvResult.append(\"\\n$it\")\n            }.catch {\n                tvResult.append(\"\\n${it.errorMsg}\")\n                //失败回调\n                it.show()\n            }.collect {\n                tvResult.append(\"\\n上传成功 : $it\")\n            }\n    }\n\n    /**\n     * android 10 及以上文件上传 ，兼容Android 10以下\n     *\n     * 注意：这里并非通过 [Await] 实现的， 而是通过 [Flow] 监听的进度，因为在监听上传进度这块，Flow性能更优，且更简单\n     *\n     * 如不需要监听进度，toFlow 方法不要传进度回调即可\n     */\n    private suspend fun AwaitFragmentBinding.uploadAndroid10(v: View) {\n        //真实环境，需要调用文件选择器，拿到Uri对象\n        val uri = Uri.parse(\"content://media/external/downloads/13417\")\n        RxHttp.postForm(Url.UPLOAD_URL)\n            .addPart(requireContext(), \"file\", uri)\n            .toFlow<String>()\n            .onProgress {\n                //上传进度回调,0-100，仅在进度有更新时才会回调\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize //当前已上传的字节大小\n                val totalSize = it.totalSize //要上传的总字节大小\n                tvResult.append(\"\\n$it\")\n            }.catch {\n                tvResult.append(\"\\n${it.errorMsg}\")\n                //失败回调\n                it.show()\n            }.collect {\n                tvResult.append(\"\\n上传成功 : $it\")\n            }\n    }\n\n    /**\n     * Android 10以下 或 下载文件到沙盒目录下，下载可以直接传入file的绝对路径\n     *\n     * 如不需要监听下载进度，toDownload 方法不要传进度回调即可\n     */\n    private suspend fun AwaitFragmentBinding.download(view: View) {\n//        val destPath = \"${requireContext().externalCacheDir}/${System.currentTimeMillis()}.apk\"\n//        RxHttp.get(Url.DOWNLOAD_URL)\n//            .toDownloadAwait(destPath) {\n//                val currentProgress = it.progress //当前进度 0-100\n//                val currentSize = it.currentSize //当前已下载的字节大小\n//                val totalSize = it.totalSize //要下载的总字节大小\n//                tvResult.append(it.toString())\n//            }.awaitResult {\n//                tvResult.append(\"\\n下载完成, $it\")\n//            }.onFailure {\n//                //异常回调\n//                tvResult.append(\"\\n${it.errorMsg}\")\n//                it.show()\n//            }\n    }\n\n    /**\n     * 断点下载\n     * Android 10以下 或 下载文件到沙盒目录下，下载可以直接传入file的绝对路径\n     * 如不需要监听下载进度，toDownload 方法不要传进度回调即可\n     */\n    private suspend fun AwaitFragmentBinding.appendDownload(view: View) {\n//        val destPath = \"${requireContext().externalCacheDir}/Miaobo.apk\"\n//        RxHttp.get(Url.DOWNLOAD_URL)\n//            .toDownloadAwait(destPath, true) {\n//                val currentProgress = it.progress //当前进度 0-100\n//                val currentSize = it.currentSize //当前已下载的字节大小\n//                val totalSize = it.totalSize //要下载的总字节大小\n//                tvResult.append(it.toString())\n//            }.awaitResult {\n//                tvResult.append(\"\\n下载完成, $it\")\n//            }.onFailure {\n//                //异常回调\n//                tvResult.append(\"\\n${it.errorMsg}\")\n//                it.show()\n//            }\n    }\n\n    /**\n     * Android 10 及以上下载，兼容Android 10以下\n     * 如不需要监听下载进度，toDownload 方法不要传进度回调即可\n     */\n    private suspend fun AwaitFragmentBinding.downloadAndroid10(view: View) {\n//        val factory = Android10DownloadFactory(requireContext(), \"miaobo.apk\")\n//        RxHttp.get(Url.DOWNLOAD_URL)\n//            .toDownloadAwait(factory) {\n//                val currentProgress = it.progress //当前进度 0-100\n//                val currentSize = it.currentSize //当前已下载的字节大小\n//                val totalSize = it.totalSize //要下载的总字节大小\n//                tvResult.append(it.toString())\n//            }.awaitResult {\n//                tvResult.append(\"\\n下载完成, $it\")\n//            }.onFailure {\n//                //异常回调\n//                tvResult.append(\"\\n${it.errorMsg}\")\n//                it.show()\n//            }\n    }\n\n    /**\n     * Android 10 及以上断点下载，兼容Android 10以下\n     * 如不需要监听下载进度，toDownload 方法不要传进度回调即可\n     */\n    private suspend fun AwaitFragmentBinding.appendDownloadAndroid10(view: View) {\n//        val factory = Android10DownloadFactory(requireContext(), \"miaobo.apk\")\n//        RxHttp.get(Url.DOWNLOAD_URL)\n//            .toDownloadAwait(factory, true) {\n//                val currentProgress = it.progress //当前进度 0-100\n//                val currentSize = it.currentSize //当前已下载的字节大小\n//                val totalSize = it.totalSize //要下载的总字节大小\n//                tvResult.append(it.toString())\n//            }.awaitResult {\n//                tvResult.append(\"\\n下载完成, $it\")\n//            }.onFailure {\n//                //异常回调\n//                tvResult.append(\"\\n${it.errorMsg}\")\n//                it.show()\n//            }\n    }\n\n\n    private fun AwaitFragmentBinding.clearLog(view: View) {\n        tvResult.text = \"\"\n        tvResult.setBackgroundColor(Color.TRANSPARENT)\n    }\n\n    override fun onClick(v: View) {\n        mBinding.run {\n            lifecycleScope.launch {\n                when (v.id) {\n                    R.id.sendGet -> sendGet(v)\n                    R.id.sendPostForm -> sendPostForm(v)\n                    R.id.sendPostJson -> sendPostJson(v)\n                    R.id.sendPostJsonArray -> sendPostJsonArray(v)\n                    R.id.xmlConverter -> xmlConverter(v)\n                    R.id.upload -> upload(v)\n                    R.id.upload10 -> uploadAndroid10(v)\n                    R.id.download -> download(v)\n                    R.id.download_append -> appendDownload(v)\n                    R.id.download10 -> downloadAndroid10(v)\n                    R.id.download10_append -> appendDownloadAndroid10(v)\n                    R.id.bt_clear -> clearLog(v)\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/fragment/BaseFragment.kt",
    "content": "package com.example.httpsender.fragment\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.LayoutRes\nimport androidx.databinding.DataBindingUtil\nimport androidx.databinding.ViewDataBinding\nimport androidx.fragment.app.Fragment\n\n/**\n * User: ljx\n * Date: 2020/6/2\n * Time: 11:45\n */\nabstract class BaseFragment<T : ViewDataBinding> : Fragment() {\n\n    @LayoutRes\n    private var layoutId = 0\n    protected lateinit var mBinding: T\n\n    fun setContentView(@LayoutRes layoutId: Int) {\n        this.layoutId = layoutId\n    }\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {\n        mBinding = DataBindingUtil.inflate(inflater, layoutId, container, false)\n        return mBinding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        mBinding.onViewCreated(savedInstanceState)\n    }\n\n    open fun T.onViewCreated(savedInstanceState: Bundle?) {\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/fragment/FlowFragment.kt",
    "content": "package com.example.httpsender.fragment\n\nimport android.graphics.Color\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.lifecycle.lifecycleScope\nimport com.example.httpsender.R\nimport com.example.httpsender.databinding.FlowFragmentBinding\nimport com.example.httpsender.entity.Article\nimport com.example.httpsender.entity.Location\nimport com.example.httpsender.entity.Name\nimport com.example.httpsender.entity.NewsDataXml\nimport com.example.httpsender.entity.PageList\nimport com.example.httpsender.entity.Url\nimport com.example.httpsender.kt.errorMsg\nimport com.example.httpsender.kt.show\nimport com.example.httpsender.parser.Android10DownloadFactory\nimport com.google.gson.Gson\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.launch\nimport rxhttp.toDownloadFlow\nimport rxhttp.toFlow\nimport rxhttp.wrapper.param.RxHttp\nimport rxhttp.wrapper.param.toFlowResponse\nimport java.io.File\n\n/**\n * 使用 协程(RxHttp + Flow) 发请求\n *\n * ```\n * RxHttp.postXxx(\"/service/...\")\n *     .add(\"key\", \"value\")\n *     .toFlow<User>()\n *     .catch {\n *        val throwable = it\n *     }.collect {\n *        val user = it\n *     }\n *```\n * User: ljx\n * Date: 2021/9/18\n * Time: 20:16\n */\nclass FlowFragment : BaseFragment<FlowFragmentBinding>(), View.OnClickListener {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.flow_fragment)\n    }\n\n    override fun FlowFragmentBinding.onViewCreated(savedInstanceState: Bundle?) {\n        click = this@FlowFragment\n    }\n\n    //发送Get请求，获取文章列表\n    private suspend fun FlowFragmentBinding.sendGet(view: View) {\n        RxHttp.get(\"/article/list/0/json\")\n            .toFlowResponse<PageList<Article>>()\n            .catch {\n                tvResult.text = it.errorMsg\n                it.show()\n            }.collect {\n                tvResult.text = Gson().toJson(it)\n            }\n    }\n\n    //发送Post表单请求,根据关键字查询文章\n    private suspend fun FlowFragmentBinding.sendPostForm(view: View) {\n        RxHttp.postForm(\"/article/query/0/json\")\n            .add(\"k\", \"性能优化\")\n            .toFlowResponse<PageList<Article>>()\n            .catch {\n                tvResult.text = it.errorMsg\n                it.show()\n            }.collect {\n                tvResult.text = Gson().toJson(it)\n            }\n    }\n\n    //发送Post Json请求，此接口不通，通过日志可以看到，发送出去的json对象\n    private suspend fun FlowFragmentBinding.sendPostJson(view: View) {\n        /*\n           发送以下User对象\n           {\"name\":\"张三\",\"sex\":1,\"height\":180,\"weight\":70,\n           \"interest\":[\"羽毛球\",\"游泳\"],\n           \"location\":{\"latitude\":30.7866,\"longitude\":120.6788},\n           \"address\":{\"street\":\"科技园路.\",\"city\":\"江苏苏州\",\"country\":\"中国\"}}\n         */\n        val interestList: MutableList<String> = ArrayList() //爱好\n        interestList.add(\"羽毛球\")\n        interestList.add(\"游泳\")\n        val address = \"\"\"\n            {\"street\":\"科技园路.\",\"city\":\"江苏苏州\",\"country\":\"中国\"}\n        \"\"\".trimIndent()\n        RxHttp.postJson(\"/article/list/0/json\")\n            .add(\"name\", \"张三\")\n            .add(\"sex\", 1)\n            .addAll(\"\"\"{\"height\":180,\"weight\":70}\"\"\") //通过addAll系列方法添加多个参数\n            .add(\"interest\", interestList) //添加数组对象\n            .add(\"location\", Location(120.6788, 30.7866)) //添加位置对象\n            .addJsonElement(\"address\", address) //通过字符串添加一个对象\n            .toFlow<String>()\n            .catch {\n                tvResult.text = it.errorMsg\n                it.show()\n            }.collect {\n                tvResult.text = it\n            }\n    }\n\n    //发送Post JsonArray请求，通过日志可以看到，发送出去的json数组\n    private suspend fun FlowFragmentBinding.sendPostJsonArray(view: View) {\n        /*\n           发送以下Json数组\n           [{\"name\":\"张三\"},{\"name\":\"李四\"},{\"name\":\"王五\"},{\"name\":\"赵六\"},{\"name\":\"杨七\"}]\n         */\n        val names: MutableList<Name?> = ArrayList()\n        names.add(Name(\"赵六\"))\n        names.add(Name(\"杨七\"))\n        RxHttp.postJsonArray(\"/article/list/0/json\")\n            .add(\"name\", \"张三\")\n            .add(Name(\"李四\"))\n            .addJsonElement(\"\"\"{\"name\":\"王五\"}\"\"\")\n            .addAll(names)\n            .toFlow<String>()\n            .catch {\n                tvResult.text = it.errorMsg\n                it.show()\n            }.collect {\n                tvResult.text = it\n            }\n\n    }\n\n    //此接口不同，但通过日志可以看到，发送出去的是xml数据，如果收到也是xml数据，则会自动解析为我们指定的对象\n    private suspend fun FlowFragmentBinding.xmlConverter(view: View) {\n        RxHttp.postBody(\"http://webservices.nextbus.com/service/publicXMLFeed?command=routeConfig&a=sf-muni\")\n            .setBody(Name(\"张三\"))\n            .setXmlConverter()\n            .toFlow<NewsDataXml>()\n            .catch {\n                tvResult.text = it.errorMsg\n                it.show()\n            }.collect {\n                tvResult.text = Gson().toJson(it)\n            }\n    }\n\n    /**\n     * android 10之前 或 沙盒目录(Android/data/packageName/)下的文件上传\n     *\n     * 如不需要监听进度，toFlow 方法不要传进度回调即可\n     */\n    private suspend fun FlowFragmentBinding.upload(v: View) {\n        RxHttp.postForm(Url.UPLOAD_URL)\n            .addFile(\"file\", File(\"xxxx/1.png\"))\n            .toFlow<String>()\n            .onProgress {\n                //上传进度回调,0-100，仅在进度有更新时才会回调\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize //当前已上传的字节大小\n                val totalSize = it.totalSize //要上传的总字节大小\n                tvResult.append(\"\\n$it\")\n            }.catch {\n                tvResult.append(\"\\n${it.errorMsg}\")\n                //失败回调\n                it.show()\n            }.collect {\n                tvResult.append(\"\\n上传成功 : $it\")\n            }\n    }\n\n    /**\n     * android 10 及以上文件上传 ，兼容Android 10以下\n     *\n     * 如不需要监听进度，toFlow 方法不要传进度回调即可\n     */\n    private suspend fun FlowFragmentBinding.uploadAndroid10(v: View) {\n        //真实环境，需要调用文件选择器，拿到Uri对象\n        val uri = Uri.parse(\"content://media/external/downloads/13417\")\n        RxHttp.postForm(Url.UPLOAD_URL)\n            .addPart(requireContext(), \"file\", uri)\n            .toFlow<String>()\n            .onProgress {\n                //上传进度回调,0-100，仅在进度有更新时才会回调\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize //当前已上传的字节大小\n                val totalSize = it.totalSize //要上传的总字节大小\n                tvResult.append(\"\\n$it\")\n            }.catch {\n                tvResult.append(\"\\n${it.errorMsg}\")\n                //失败回调\n                it.show()\n            }.collect {\n                tvResult.append(\"\\n上传成功 : $it\")\n            }\n    }\n\n    /**\n     * Android 10以下 或 下载文件到沙盒目录下，下载可以直接传入file的绝对路径\n     *\n     * 如不需要监听下载进度，toFlow 方法不要传进度回调即可\n     */\n    private suspend fun FlowFragmentBinding.download(view: View) {\n        val destPath = \"${requireContext().externalCacheDir}/${System.currentTimeMillis()}.apk\"\n        RxHttp.get(Url.DOWNLOAD_URL)\n            .toDownloadFlow(destPath)\n            .onProgress {\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize //当前已下载的字节大小\n                val totalSize = it.totalSize //要下载的总字节大小\n                tvResult.append(\"\\n$it\")\n            }.catch {\n                tvResult.append(\"\\n${it.errorMsg}\") //异常回调\n                it.show()\n            }.collect {\n                tvResult.append(\"\\n下载完成, $it\")\n            }\n    }\n\n    /**\n     * 断点下载\n     * Android 10以下 或 下载文件到沙盒目录下，下载可以直接传入file的绝对路径\n     *\n     * 如不需要监听下载进度，toFlow 方法不要传进度回调即可\n     */\n    private suspend fun FlowFragmentBinding.appendDownload(view: View) {\n        val destPath = \"${requireContext().externalCacheDir}/Miaobo.apk\"\n        RxHttp.get(Url.DOWNLOAD_URL)\n            .toDownloadFlow(destPath, true)\n            .onProgress {\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize //当前已下载的字节大小\n                val totalSize = it.totalSize //要下载的总字节大小\n                tvResult.append(\"\\n$it\")\n            }.catch {\n                tvResult.append(\"\\n${it.errorMsg}\")\n                it.show()\n            }.collect {\n                tvResult.append(\"\\n下载完成, $it\")\n            }\n    }\n\n    /**\n     * Android 10 及以上下载，兼容Android 10以下\n     *\n     * 如不需要监听下载进度，toFlow 方法不要传进度回调即可\n     */\n    private suspend fun FlowFragmentBinding.downloadAndroid10(view: View) {\n        val factory = Android10DownloadFactory(requireContext(), \"miaobo.apk\")\n        RxHttp.get(Url.DOWNLOAD_URL)\n            .toDownloadFlow(factory)\n            .onProgress {\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize //当前已下载的字节大小\n                val totalSize = it.totalSize //要下载的总字节大小\n                tvResult.append(\"\\n$it\")\n            }.catch {\n                tvResult.append(\"\\n${it.errorMsg}\")\n                it.show()\n            }.collect {\n                tvResult.append(\"\\n下载完成, $it\")\n            }\n    }\n\n    /**\n     * Android 10 及以上断点下载，兼容Android 10以下\n     *\n     * 如不需要监听下载进度，toFlow 方法不要传进度回调即可\n     */\n    private suspend fun FlowFragmentBinding.appendDownloadAndroid10(view: View) {\n        val factory = Android10DownloadFactory(requireContext(), \"miaobo.apk\")\n        RxHttp.get(Url.DOWNLOAD_URL)\n            .toDownloadFlow(factory, true)\n            .onProgress {\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize //当前已下载的字节大小\n                val totalSize = it.totalSize //要下载的总字节大小\n                tvResult.append(\"\\n$it\")\n            }.catch {\n                //异常回调\n                tvResult.append(\"\\n${it.errorMsg}\")\n                it.show()\n            }.collect {\n                tvResult.append(\"\\n下载完成, $it\")\n            }\n    }\n\n\n    private fun FlowFragmentBinding.clearLog(view: View) {\n        tvResult.text = \"\"\n        tvResult.setBackgroundColor(Color.TRANSPARENT)\n    }\n\n    override fun onClick(v: View) {\n        mBinding.run {\n            lifecycleScope.launch {\n                when (v.id) {\n                    R.id.sendGet -> sendGet(v)\n                    R.id.sendPostForm -> sendPostForm(v)\n                    R.id.sendPostJson -> sendPostJson(v)\n                    R.id.sendPostJsonArray -> sendPostJsonArray(v)\n                    R.id.xmlConverter -> xmlConverter(v)\n                    R.id.upload -> upload(v)\n                    R.id.upload10 -> uploadAndroid10(v)\n                    R.id.download -> download(v)\n                    R.id.download_append -> appendDownload(v)\n                    R.id.download10 -> downloadAndroid10(v)\n                    R.id.download10_append -> appendDownloadAndroid10(v)\n                    R.id.bt_clear -> clearLog(v)\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/fragment/MultiDownloadFragment.java",
    "content": "package com.example.httpsender.fragment;\n\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.View.OnClickListener;\n\nimport androidx.annotation.Nullable;\nimport androidx.recyclerview.widget.RecyclerView;\nimport androidx.recyclerview.widget.SimpleItemAnimator;\n\nimport com.example.httpsender.DownloadMultiAdapter;\nimport com.example.httpsender.DownloadMultiAdapter.OnItemClickListener;\nimport com.example.httpsender.R;\nimport com.example.httpsender.Tip;\nimport com.example.httpsender.databinding.MultiDownloadFragmentBinding;\nimport com.example.httpsender.entity.DownloadTask;\nimport com.example.httpsender.vm.MultiTaskDownloader;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.ArrayList;\n\n/**\n * User: ljx\n * Date: 2021/9/25\n * Time: 18:08\n */\npublic class MultiDownloadFragment extends BaseFragment<MultiDownloadFragmentBinding> implements OnItemClickListener<DownloadTask>, OnClickListener {\n\n    private final String[] downloadUrl = {\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/uS12nZLR.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/BYGanTMW.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/Iu9hZLL8.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/DdKLk5VX.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/Byww5X8k.ts\",\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?111\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?222\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?333\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?444\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?555\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?666\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?777\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?888\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?999\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?101\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?102\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?103\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?104\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?105\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?106\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?107\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?108\",//探探\n        \"https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk\",//探探\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/OUkREagY.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/ZJUsgPSd.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/I5ivzoXR.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/PFPXapY7.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/tJj2JTVy.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/mj2fFYjH.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/MOXijkzw.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/uiwVyFej.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/HijAOXaK.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/h2mFS6ef.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/vf8fjmJd.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/0jsVXFSa.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/aZfnIVnP.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/l7cddTCQ.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/QaMi23d0.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/ljLywei6.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/FQpwzm4U.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/4oW2C2iZ.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/57OL3KeG.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/vYlV9nTw.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/XUmd5HWF.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/btVvEY5r.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/eJRKGoaP.ts\",\n//        \"https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/iz5kx1X1.ts\",\n    };\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.multi_download_fragment);\n    }\n\n    @Override\n    public void onViewCreated(@NotNull MultiDownloadFragmentBinding binding, @Nullable Bundle savedInstanceState) {\n        super.onViewCreated(binding, savedInstanceState);\n        binding.setClick(this);\n        ArrayList<DownloadTask> allTask = new ArrayList<>();  //所有下载任务\n        for (int i = 0; i < downloadUrl.length; i++) {\n            String url = downloadUrl[i];\n            DownloadTask task = new DownloadTask(url);\n            String suffix = url.substring(url.lastIndexOf(\".\"));\n            task.setLocalPath(getContext().getExternalCacheDir() + \"/\" + i + suffix);\n            allTask.add(task);\n        }\n        RecyclerView recyclerView = binding.recyclerView;\n\n        MultiTaskDownloader.addTasks(allTask);\n        DownloadMultiAdapter multiAdapter = new DownloadMultiAdapter(MultiTaskDownloader.getAllTask());\n        multiAdapter.setOnItemClickListener(this);\n        recyclerView.setAdapter(multiAdapter);\n        ((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);\n\n        MultiTaskDownloader.getLiveTask().observe(getViewLifecycleOwner(), task -> {\n            int index = MultiTaskDownloader.getAllTask().indexOf(task);\n            if (index != -1) {\n                //任务有更新，刷新单个item\n                multiAdapter.notifyItemChanged(index);\n            }\n        });\n    }\n\n    @Override\n    public void onClick(View v) {\n        int id = v.getId();\n        if (id == R.id.start_all) {\n            MultiTaskDownloader.startAllDownloadTask();\n        } else if (id == R.id.cancel_all) {\n            MultiTaskDownloader.cancelAllTask();\n        }\n    }\n\n    @Override\n    public void onItemClick(View view, DownloadTask task, int position) {\n        if (view.getId() == R.id.bt_pause) {\n            int curState = task.getState();  //任务当前状态\n            if (curState == MultiTaskDownloader.IDLE         //未开始->开始下载\n                || curState == MultiTaskDownloader.PAUSED    //暂停下载->继续下载\n                || curState == MultiTaskDownloader.CANCEL    //已取消->重新开始下载\n                || curState == MultiTaskDownloader.FAIL      //下载失败->重新下载\n            ) {\n                MultiTaskDownloader.download(task);\n            } else if (curState == MultiTaskDownloader.WAITING) {       //等待中->取消下载\n                MultiTaskDownloader.removeWaitTask(task);\n            } else if (curState == MultiTaskDownloader.DOWNLOADING) {   //下载中->暂停下载\n                MultiTaskDownloader.pauseTask(task);\n            } else if (curState == MultiTaskDownloader.COMPLETED) {   //任务已完成\n                Tip.show(\"该任务已完成\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/fragment/RxJavaFragment.kt",
    "content": "package com.example.httpsender.fragment\n\nimport android.graphics.Color\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport com.example.httpsender.R\nimport com.example.httpsender.databinding.RxjavaFragmentBinding\nimport com.example.httpsender.entity.Article\nimport com.example.httpsender.entity.Location\nimport com.example.httpsender.entity.Name\nimport com.example.httpsender.entity.NewsDataXml\nimport com.example.httpsender.entity.PageList\nimport com.example.httpsender.entity.Url\nimport com.example.httpsender.kt.errorMsg\nimport com.example.httpsender.kt.show\nimport com.example.httpsender.parser.Android10DownloadFactory\nimport com.google.gson.Gson\nimport com.rxjava.rxlife.life\nimport com.rxjava.rxlife.lifeOnMain\nimport rxhttp.wrapper.param.RxHttp\nimport rxhttp.wrapper.param.toObservable\nimport rxhttp.wrapper.param.toObservableResponse\nimport java.io.File\n\n/**\n * 使用 RxHttp + RxJava 发请求\n *\n * ```\n * RxHttp.postXxx(\"/service/...\")\n *     .add(\"key\", \"value\")\n *     .toObservable<User>()\n *     .subscribe(user -> {\n *\n *     }, throwable -> {\n *\n *     })\n * ```\n * User: ljx\n * Date: 2020/4/24\n * Time: 18:16\n */\nclass RxJavaFragment : BaseFragment<RxjavaFragmentBinding>(), View.OnClickListener {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.rxjava_fragment)\n    }\n\n    override fun RxjavaFragmentBinding.onViewCreated(savedInstanceState: Bundle?) {\n        click = this@RxJavaFragment\n    }\n\n    //发送Get请求，获取文章列表\n    fun RxjavaFragmentBinding.sendGet(view: View?) {\n        RxHttp.get(\"/article/list/0/json\")\n            .addQuery(\"aa\")\n            .addQuery(\"bb\",\"\")\n            .toObservableResponse<PageList<Article>>()\n            .lifeOnMain(this@RxJavaFragment)\n            .subscribe({\n                tvResult.text = Gson().toJson(it)\n            }, {\n                tvResult.text = it.errorMsg\n                it.show()\n            })\n    }\n\n    //发送Post表单请求,根据关键字查询文章\n    fun RxjavaFragmentBinding.sendPostForm(view: View?) {\n        RxHttp.postForm(\"/article/query/0/json\")\n            .add(\"k\", \"性能优化\")\n            .toObservableResponse<PageList<Article>>()\n            .lifeOnMain(this@RxJavaFragment) //感知生命周期，并在主线程回调\n            .subscribe({\n                tvResult.text = Gson().toJson(it)\n            }, {\n                tvResult.text = it.errorMsg\n                it.show()\n            })\n    }\n\n    //发送Post Json请求，此接口不通，通过日志可以看到，发送出去的json对象\n    fun RxjavaFragmentBinding.sendPostJson(view: View?) {\n        //发送以下User对象\n        /*\n           {\n               \"name\": \"张三\",\n               \"sex\": 1,\n               \"height\": 180,\n               \"weight\": 70,\n               \"interest\": [\n                   \"羽毛球\",\n                   \"游泳\"\n               ],\n               \"location\": {\n                   \"latitude\": 30.7866,\n                   \"longitude\": 120.6788\n               },\n               \"address\": {\n                   \"street\": \"科技园路.\",\n                   \"city\": \"江苏苏州\",\n                   \"country\": \"中国\"\n               }\n           }\n         */\n        val interestList: MutableList<String> = ArrayList() //爱好\n        interestList.add(\"羽毛球\")\n        interestList.add(\"游泳\")\n        val address = \"{\\\"street\\\":\\\"科技园路.\\\",\\\"city\\\":\\\"江苏苏州\\\",\\\"country\\\":\\\"中国\\\"}\"\n        RxHttp.postJson(\"/article/list/0/json\")\n            .add(\"name\", \"张三\")\n            .add(\"sex\", 1)\n            .addAll(\"{\\\"height\\\":180,\\\"weight\\\":70}\") //通过addAll系列方法添加多个参数\n            .add(\"interest\", interestList) //添加数组对象\n            .add(\"location\", Location(120.6788, 30.7866)) //添加位置对象\n            .addJsonElement(\"address\", address) //通过字符串添加一个对象\n            .toObservableString()\n            .lifeOnMain(this@RxJavaFragment)//感知生命周期，并在主线程回调\n            .subscribe({\n                tvResult.text = it\n            }, {\n                tvResult.text = it.errorMsg\n                it.show()\n            })\n    }\n\n    //发送Post JsonArray请求，通过日志可以看到，发送出去的json数组\n    fun RxjavaFragmentBinding.sendPostJsonArray(view: View?) {\n        //发送以下Json数组\n        /*\n           [\n               {\n                   \"name\": \"张三\"\n               },\n               {\n                   \"name\": \"李四\"\n               },\n               {\n                   \"name\": \"王五\"\n               },\n               {\n                   \"name\": \"赵六\"\n               },\n               {\n                   \"name\": \"杨七\"\n               }\n           ]\n         */\n        val names: MutableList<Name?> = ArrayList()\n        names.add(Name(\"赵六\"))\n        names.add(Name(\"杨七\"))\n        RxHttp.postJsonArray(\"/article/list/0/json\")\n            .add(\"name\", \"张三\")\n            .add(Name(\"李四\"))\n            .addJsonElement(\"{\\\"name\\\":\\\"王五\\\"}\")\n            .addAll(names)\n            .toObservableString()\n            .lifeOnMain(this@RxJavaFragment)\n            .subscribe({\n                tvResult.text = it\n            }, {\n                tvResult.text = it.errorMsg\n                //失败回调\n                it.show()\n            })\n    }\n\n    //此接口不同，但通过日志可以看到，发送出去的是xml数据，如果收到也是xml数据，则会自动解析为我们指定的对象\n    fun RxjavaFragmentBinding.xmlConverter(view: View?) {\n        RxHttp.postBody(\"http://webservices.nextbus.com/service/publicXMLFeed?command=routeConfig&a=sf-muni\")\n            .setBody(Name(\"张三\"))\n            .setXmlConverter()\n            .toObservable<NewsDataXml>()\n            .lifeOnMain(this@RxJavaFragment) //感知生命周期，并在主线程回调\n            .subscribe({\n                tvResult.text = Gson().toJson(it)\n            }, {\n                tvResult.text = it.errorMsg\n                //失败回调\n                it.show()\n            })\n    }\n\n    /**\n     * android 10之前 或 沙盒目录(Android/data/packageName/)下的文件上传，如不需要监听进度，注释掉 upload 方法即可\n     */\n    private fun RxjavaFragmentBinding.upload(v: View) {\n        RxHttp.postForm(Url.UPLOAD_URL)\n            .addFile(\"file\", File(\"xxxx/1.png\"))\n            .toObservableString()\n            .onMainProgress {\n                //上传进度回调,0-100，仅在进度有更新时才会回调\n                val currentProgress = it.progress  //当前进度 0-100\n                val currentSize = it.currentSize //当前已上传的字节大小\n                val totalSize = it.totalSize     //要上传的总字节大小\n                tvResult.append(\"\\n$it\")\n            }\n            .life(this@RxJavaFragment)    //页面销毁，自动关闭请求\n            .subscribe({\n                tvResult.append(\"\\n上传成功 : $it\")\n            }, {\n                tvResult.append(\"\\n${it.errorMsg}\")\n                it.show()\n            })\n    }\n\n    /**\n     * android 10 及以上文件上传 ，兼容Android 10以下，如不需要监听进度，注释掉 upload 方法即可\n     */\n    private fun RxjavaFragmentBinding.uploadAndroid10(v: View) {\n        //真实环境，需要调用文件选择器，拿到Uri对象\n        val uri = Uri.parse(\"content://media/external/downloads/13417\")\n        RxHttp.postForm(Url.UPLOAD_URL)\n            .addPart(requireContext(), \"file\", uri)\n            .toObservableString()\n            .onMainProgress {\n                //上传进度回调,0-100，仅在进度有更新时才会回调\n                val currentProgress = it.progress  //当前进度 0-100\n                val currentSize = it.currentSize //当前已上传的字节大小\n                val totalSize = it.totalSize     //要上传的总字节大小\n                tvResult.append(\"\\n$it\")\n            }\n            .life(this@RxJavaFragment)   //页面销毁，自动关闭请求\n            .subscribe({\n                tvResult.append(\"\\n上传成功 : $it\")\n            }, {\n                tvResult.append(\"\\n${it.errorMsg}\")  //失败回调\n                it.show()\n            })\n    }\n\n\n    /**\n     * Android 10以下 或 下载文件到沙盒目录下，下载可以直接传入file的绝对路径\n     *\n     * 如不需要监听下载进度，asDownload 方法不要传进度回调即可\n     */\n    private fun RxjavaFragmentBinding.download(view: View) {\n        val destPath = \"${requireContext().externalCacheDir}/${System.currentTimeMillis()}.apk\"\n        RxHttp.get(Url.DOWNLOAD_URL)\n            .toDownloadObservable(destPath)\n            .onMainProgress {\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize //当前已下载的字节大小\n                val totalSize = it.totalSize //要下载的总字节大小\n                tvResult.append(\"\\n$it\")\n            }\n            .life(this@RxJavaFragment) //感知生命周期，并在主线程回调\n            .subscribe({\n                tvResult.append(\"\\n下载完成, $it\")\n            }, {\n                //下载失败\n                tvResult.append(\"\\n${it.errorMsg}\")\n                it.show()\n            })\n    }\n\n    /**\n     * 断点下载\n     * Android 10以下 或 下载文件到沙盒目录下，下载可以直接传入file的绝对路径\n     * 如不需要监听下载进度，asAppendDownload 方法不要传进度回调即可\n     */\n    private fun RxjavaFragmentBinding.appendDownload(view: View) {\n        val destPath = \"${requireContext().externalCacheDir}/Miaobo.apk\"\n        RxHttp.get(Url.DOWNLOAD_URL)\n            .toDownloadObservable(destPath, true)\n            .onMainProgress {\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize //当前已下载的字节大小\n                val totalSize = it.totalSize //要下载的总字节大小\n                tvResult.append(\"\\n$it\")\n            }\n            .life(this@RxJavaFragment) //感知生命周期，并在主线程回调\n            .subscribe({\n                tvResult.append(\"\\n下载完成, $it\")\n            }, {\n                //下载失败\n                tvResult.append(\"\\n${it.errorMsg}\")\n                it.show()\n            })\n    }\n\n\n    /**\n     * Android 10 及以上下载，兼容Android 10以下\n     * 如不需要监听下载进度，asDownload 方法不要传进度回调即可\n     */\n    private fun RxjavaFragmentBinding.downloadAndroid10(view: View) {\n        val factory = Android10DownloadFactory(requireContext(), \"miaobo.apk\")\n        RxHttp.get(Url.DOWNLOAD_URL)\n            .toDownloadObservable(factory)\n            .onMainProgress {\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize //当前已下载的字节大小\n                val totalSize = it.totalSize //要下载的总字节大小\n                tvResult.append(\"\\n$it\")\n            }\n            .life(this@RxJavaFragment) //感知生命周期，并在主线程回调\n            .subscribe({\n                tvResult.append(\"\\n下载完成, $it\")\n            }, {\n                //下载失败\n                tvResult.append(\"\\n${it.errorMsg}\")\n                it.show()\n            })\n    }\n\n    /**\n     * Android 10 及以上断点下载，兼容Android 10以下\n     * 如不需要监听下载进度，asAppendDownload 方法不要传进度回调即可\n     */\n    private fun RxjavaFragmentBinding.appendDownloadAndroid10(view: View) {\n        val factory = Android10DownloadFactory(requireContext(), \"miaobo.apk\")\n        RxHttp.get(Url.DOWNLOAD_URL)\n            .toDownloadObservable(factory, true)\n            .onMainProgress {\n                val currentProgress = it.progress //当前进度 0-100\n                val currentSize = it.currentSize //当前已下载的字节大小\n                val totalSize = it.totalSize //要下载的总字节大小\n                tvResult.append(\"\\n$it\")\n            }\n            .life(this@RxJavaFragment) //感知生命周期，并在主线程回调\n            .subscribe({\n                tvResult.append(\"\\n下载完成, $it\")\n            }, {\n                //下载失败\n                tvResult.append(\"\\n${it.errorMsg}\")\n                it.show()\n            })\n    }\n\n    private fun RxjavaFragmentBinding.clearLog(view: View?) {\n        tvResult.text = \"\"\n        tvResult.setBackgroundColor(Color.TRANSPARENT)\n    }\n\n    override fun onClick(v: View) {\n        mBinding.apply {\n            when (v.id) {\n                R.id.sendGet -> sendGet(v)\n                R.id.sendPostForm -> sendPostForm(v)\n                R.id.sendPostJson -> sendPostJson(v)\n                R.id.sendPostJsonArray -> sendPostJsonArray(v)\n                R.id.xmlConverter -> xmlConverter(v)\n                R.id.upload -> upload(v)\n                R.id.upload10 -> uploadAndroid10(v)\n                R.id.download -> download(v)\n                R.id.download_append -> appendDownload(v)\n                R.id.download10 -> downloadAndroid10(v)\n                R.id.download10_append -> appendDownloadAndroid10(v)\n                R.id.bt_clear -> clearLog(v)\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/interceptor/RedirectInterceptor.java",
    "content": "package com.example.httpsender.interceptor;\n\nimport java.io.IOException;\n\nimport okhttp3.Interceptor;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\n/**\n * 处理重定向的拦截器，非必须\n * User: ljx\n * Date: 2019-12-17\n * Time: 23:24\n */\npublic class RedirectInterceptor implements Interceptor {\n\n    @Override\n    public Response intercept(Chain chain) throws IOException {\n        okhttp3.Request request = chain.request();\n        Response response = chain.proceed(request);\n        int code = response.code();\n        if (code == 308) {\n            //获取重定向的地址\n            String location = response.headers().get(\"Location\");\n            //重新构建请求\n            Request newRequest = request.newBuilder().url(location).build();\n            response.close();\n            response = chain.proceed(newRequest);\n        }\n        return response;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/interceptor/TokenInterceptor.java",
    "content": "package com.example.httpsender.interceptor;\n\n\nimport com.example.httpsender.entity.User;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport okhttp3.Interceptor;\nimport okhttp3.Request;\nimport okhttp3.Response;\nimport rxhttp.wrapper.param.RxHttp;\n\n/**\n * token 失效，自动刷新token，然后再次发送请求，用户无感知\n * User: ljx\n * Date: 2019-12-04\n * Time: 11:56\n */\npublic class TokenInterceptor implements Interceptor {\n\n    //保存刷新后的token\n    private final AtomicReference<String> atomicToken = new AtomicReference<>();\n\n    //token刷新时间\n    @NotNull\n    @Override\n    public Response intercept(Chain chain) throws IOException {\n        Request request = chain.request();\n        Response originalResponse = chain.proceed(request);\n        String code = originalResponse.header(\"xxx\"); //其中xxx，自己跟服务端协定\n        if (\"-1\".equals(code)) { //token 失效 这里根据自己的业务需求写判断条件\n            atomicToken.set(null);\n            return handleTokenInvalid(chain, request);\n        }\n        return originalResponse;\n    }\n\n    //处理token失效问题, 同步刷新\n    private Response handleTokenInvalid(Chain chain, Request request) throws IOException {\n        boolean success = refreshToken();\n        Request newRequest;\n        if (success) { //刷新成功，重新添加token\n            newRequest = request.newBuilder()\n                .header(\"xxx\", atomicToken.get())  //其中xxx，自己跟服务端协定\n                .build();\n        } else {\n            newRequest = request;\n        }\n        return chain.proceed(newRequest);\n    }\n\n    //刷新token, 考虑到有并发情况，故这里需要加锁\n    private boolean refreshToken() {\n        //token不等于null，说明已经刷新\n        if (atomicToken.get() != null) return true;\n        synchronized (this) {\n            //再次判断是否已经刷新\n            if (atomicToken.get() != null) return true;\n            try {\n                //根据自己业务，同步刷新token, 注意这里千万不能异步\n                String token = RxHttp.postForm(\"/refreshToken/...\")\n                    .executeString();\n                atomicToken.set(token);\n                User.get().setToken(token); //保存最新的token\n                return true;\n            } catch (IOException e) {\n                return false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/kt/Activity.kt",
    "content": "package com.example.httpsender.kt\n\nimport android.app.Activity\nimport android.content.Intent\nimport androidx.fragment.app.Fragment\nimport kotlin.reflect.KClass\n\n/**\n * User: ljx\n * Date: 2020/5/15\n * Time: 16:33\n */\n\nfun <T : Activity> Activity.startActivity(clazz: KClass<T>, block: (Intent.() -> Unit)? = null) {\n    val intent = Intent(this, clazz.java).apply {\n        block?.invoke(this)\n    }\n    startActivity(intent)\n}\n\nfun <T : Activity> Fragment.startActivity(clazz: KClass<T>, block: (Intent.() -> Unit)? = null) {\n    val intent = Intent(activity, clazz.java).apply {\n        block?.invoke(this)\n    }\n    startActivity(intent)\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/kt/KotlinExtensions.kt",
    "content": "package com.example.httpsender.kt\n\nimport android.content.Context\nimport android.net.ConnectivityManager\nimport com.example.httpsender.AppHolder\nimport com.example.httpsender.Tip\nimport com.google.gson.JsonSyntaxException\nimport kotlinx.coroutines.TimeoutCancellationException\nimport rxhttp.wrapper.exception.HttpStatusCodeException\nimport rxhttp.wrapper.exception.ParseException\nimport java.net.ConnectException\nimport java.net.SocketTimeoutException\nimport java.net.UnknownHostException\nimport java.util.concurrent.TimeoutException\n\n/**\n * User: ljx\n * Date: 2020-02-07\n * Time: 21:04\n */\nfun Throwable.show() {\n    errorMsg.show()\n}\n\nfun String.show() {\n    Tip.show(this)\n}\n\nval Throwable.errorCode: Int\n    get() =\n        when (this) {\n            is HttpStatusCodeException -> this.statusCode //Http状态码异常\n            is ParseException -> this.errorCode.toIntOrNull() ?: -1     //业务code异常\n            else -> -1\n        }\n\nval Throwable.errorMsg: String\n    get() {\n        return if (this is UnknownHostException) { //网络异常\n            if (!isNetworkConnected(AppHolder.getInstance()))\n                \"当前无网络，请检查你的网络设置\"\n            else\n                \"网络连接不可用，请稍后重试！\"\n        } else if (\n            this is SocketTimeoutException  //okhttp全局设置超时\n            || this is TimeoutException     //rxjava中的timeout方法超时\n            || this is TimeoutCancellationException  //协程超时\n        ) {\n            \"连接超时,请稍后再试\"\n        } else if (this is ConnectException) {\n            \"网络不给力，请稍候重试！\"\n        } else if (this is HttpStatusCodeException) {               //请求失败异常\n            \"Http状态码异常 $message\"\n        } else if (this is JsonSyntaxException) {  //请求成功，但Json语法异常,导致解析失败\n            \"数据解析失败,请检查数据是否正确\"\n        } else if (this is ParseException) {       // ParseException异常表明请求成功，但是数据不正确\n            this.message ?: errorCode   //msg为空，显示code\n        } else {\n            message ?: this.toString()\n        }\n    }\n\nprivate fun isNetworkConnected(context: Context): Boolean {\n    val mConnectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager\n    val mNetworkInfo = mConnectivityManager.activeNetworkInfo\n    if (mNetworkInfo != null) {\n        return mNetworkInfo.isAvailable\n    }\n    return false\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/kt/Uri.kt",
    "content": "package com.example.httpsender.kt\n\nimport android.content.Context\nimport android.net.Uri\nimport android.provider.MediaStore\nimport android.util.Log\n\n/**\n * User: ljx\n * Date: 2020/9/24\n * Time: 15:33\n */\n\nfun Uri.dimQuery(context: Context, displayName: String) {\n    context.contentResolver.query(this, null,\n        \"_display_name LIKE '%$displayName%'\",null, null)?.use {\n        while (it.moveToNext()) {\n            val id = it.getString(it.getColumnIndex(MediaStore.MediaColumns._ID))\n            val name = it.getString(it.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME))\n            val data = it.getString(it.getColumnIndex(MediaStore.MediaColumns.DATA))\n            //注意: 通过这种方式获取的文件size，在文件被手动删除后，读取到的是不准确的\n            val size = it.getString(it.getColumnIndex(MediaStore.MediaColumns.SIZE))\n            val dateAdded = it.getString(it.getColumnIndex(MediaStore.MediaColumns.DATE_ADDED))\n            val dateModified = it.getString(it.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED))\n            Log.e(\"LJX\", \"id=$id  size=$size  name=$name  data=$data  dateAdded=$dateAdded  dateModified=$dateModified\")\n        }\n    }\n}\n\nfun Uri.dimDelete(context: Context, displayName: String) {\n    val delete = context.contentResolver.delete(this, \"_display_name LIKE '%$displayName%'\", null)\n    Log.e(\"LJX\", \"delete=$delete\")\n}\n\nfun Uri.delete(context: Context, displayName: String) {\n    val delete = context.contentResolver.delete(this, \"_display_name=?\", arrayOf(displayName))\n    Log.e(\"LJX\", \"delete=$delete\")\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/param/GetEncryptParam.java",
    "content": "package com.example.httpsender.param;\n\nimport android.graphics.Point;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\n\nimport okhttp3.HttpUrl;\nimport rxhttp.wrapper.annotation.Param;\nimport rxhttp.wrapper.entity.KeyValuePair;\nimport rxhttp.wrapper.param.Method;\nimport rxhttp.wrapper.param.NoBodyParam;\n\n/**\n * 加密get请求\n * User: ljx\n * Date: 2019-09-12\n * Time: 17:25\n */\n@Param(methodName = \"getEncrypt\")\npublic class GetEncryptParam extends NoBodyParam {\n\n    public GetEncryptParam(String url) {\n        super(url, Method.GET);\n    }\n\n    @SafeVarargs\n    public final <T extends Point, R extends CharSequence> GetEncryptParam test(List<R> a, Map<T, R> map, T[]... b) throws IOException, IllegalArgumentException {\n        return this;\n    }\n\n    @Override\n    public HttpUrl getHttpUrl() {\n        StringBuilder paramsBuilder = new StringBuilder(); //存储加密后的参数\n        List<KeyValuePair> queryParam = getQueryParam();\n        if (queryParam != null) {\n            for (KeyValuePair pair : getQueryParam()) {\n                //这里遍历所有添加的参数，可对参数进行加密操作\n                String key = pair.getKey();\n                Object value = pair.getValue();\n                //加密逻辑自己写\n            }\n        }\n        String simpleUrl = getSimpleUrl();  //拿到请求Url\n        if (paramsBuilder.length() == 0) return HttpUrl.get(simpleUrl);\n        return HttpUrl.get(simpleUrl + \"?\" + paramsBuilder);  //将加密后的参数和url组拼成HttpUrl对象并返回\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/param/PostEncryptFormParam.java",
    "content": "package com.example.httpsender.param;\n\nimport rxhttp.wrapper.annotation.Param;\nimport rxhttp.wrapper.param.FormParam;\nimport rxhttp.wrapper.param.Method;\n\n/**\n * 此类中自己声明的所有public方法(构造方法除外)都会在RxHttp$PostEncryptFormParam类中一一生成，\n * 并一一对应调用。如: RxHttp$PostEncryptFormParam.test(int,int)方法内部会调用本类的test(int,int)方法\n * User: ljx\n * Date: 2019-09-11\n * Time: 11:52\n */\n@Param(methodName = \"postEncryptForm\")\npublic class PostEncryptFormParam extends FormParam {\n\n    public PostEncryptFormParam(String url) {\n        super(url, Method.POST);\n    }\n\n    public PostEncryptFormParam(String url, Method method) {\n        super(url, method);\n    }\n\n    public PostEncryptFormParam test1(String s) {\n        return this;\n    }\n\n    //此方法会在\n    public PostEncryptFormParam test2(long a, float b) {\n        return this;\n    }\n\n    public int add(int a, int b) {\n        return a + b;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/param/PostEncryptJsonParam.kt",
    "content": "package com.example.httpsender.param\n\n\nimport okhttp3.MediaType\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.RequestBody\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport rxhttp.wrapper.annotation.Param\nimport rxhttp.wrapper.param.JsonParam\nimport rxhttp.wrapper.param.Method\nimport rxhttp.wrapper.utils.GsonUtil\n\n/**\n * User: ljx\n * Date: 2019/1/25\n * Time: 19:32\n */\n@Param(methodName = \"postEncryptJson\")\nclass PostEncryptJsonParam(url: String) : JsonParam(url, Method.POST) {\n\n    private var MEDIA_TYPE_JSON: MediaType = \"application/json; charset=utf-8\".toMediaType()\n    /**\n     * @return 根据自己的业务需求返回对应的RequestBody\n     */\n    override fun getRequestBody(): RequestBody {\n        //我们要发送Post请求，参数以加密后的json形式发出\n        //第一步，将参数转换为Json字符串\n        val json = if (bodyParam == null) \"\" else GsonUtil.toJson(bodyParam)\n        //第二步，加密\n        val encryptByte = encrypt(json, \"RxHttp\")\n        //第三部，创建RequestBody并返回\n        return encryptByte!!.toRequestBody(MEDIA_TYPE_JSON)\n    }\n\n    /**\n     * @param content  要加密的字符串\n     * @param password 密码\n     * @return 加密后的字节数组\n     */\n    private fun encrypt(content: String, password: String): ByteArray? {\n        //加码代码省略\n        return null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/param/PostEncryptJsonParam1.java",
    "content": "package com.example.httpsender.param;\n\nimport okhttp3.RequestBody;\nimport rxhttp.wrapper.annotation.Param;\nimport rxhttp.wrapper.param.AbstractBodyParam;\nimport rxhttp.wrapper.param.Method;\n\n/**\n * User: ljx\n * Date: 2019-09-11\n * Time: 11:52\n */\n@Param(methodName = \"postEncryptJson1\")\npublic class PostEncryptJsonParam1 extends AbstractBodyParam<PostEncryptJsonParam1> {\n\n    public PostEncryptJsonParam1(String url) {\n        super(url, Method.POST);\n    }\n\n    @Override\n    public RequestBody getRequestBody() {\n        return null;\n    }\n\n    public void test() {\n\n    }\n\n    @Override\n    public PostEncryptJsonParam1 add(String key, Object value) {\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/parser/Android10DownloadFactory.kt",
    "content": "package com.example.httpsender.parser\n\nimport android.content.ContentValues\nimport android.content.Context\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Environment\nimport android.provider.MediaStore\nimport androidx.annotation.RequiresApi\nimport okhttp3.Response\nimport rxhttp.wrapper.callback.UriFactory\nimport rxhttp.wrapper.utils.query\nimport java.io.File\n\n/**\n * User: ljx\n * Date: 2020/9/11\n * Time: 17:43\n *\n * @param context Context\n * @param filename 文件名\n * @param relativePath  文件相对路径，可取值:\n * [Environment.DIRECTORY_DOWNLOADS]\n * [Environment.DIRECTORY_DCIM]\n * [Environment.DIRECTORY_PICTURES]\n * [Environment.DIRECTORY_MUSIC]\n * [Environment.DIRECTORY_MOVIES]\n * [Environment.DIRECTORY_DOCUMENTS]\n * ...\n */\nclass Android10DownloadFactory @JvmOverloads constructor(\n    context: Context,\n    private val filename: String,\n    private val relativePath: String = Environment.DIRECTORY_DOWNLOADS\n) : UriFactory(context) {\n\n    /**\n     * [MediaStore.Files.getContentUri]\n     * [MediaStore.Downloads.EXTERNAL_CONTENT_URI]\n     * [MediaStore.Audio.Media.EXTERNAL_CONTENT_URI]\n     * [MediaStore.Video.Media.EXTERNAL_CONTENT_URI]\n     * [MediaStore.Images.Media.EXTERNAL_CONTENT_URI]\n     */\n    @RequiresApi(Build.VERSION_CODES.Q)\n    fun getInsertUri() = MediaStore.Downloads.EXTERNAL_CONTENT_URI\n\n    override fun query(): Uri? {\n        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            getInsertUri().query(context, filename, relativePath)\n        } else {\n            val file = File(\"${Environment.getExternalStorageDirectory()}/$relativePath/$filename\")\n            Uri.fromFile(file)\n        }\n    }\n\n    override fun insert(response: Response): Uri {\n        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            val uri = getInsertUri().query(context, filename, relativePath)\n            /*\n             * 通过查找，要插入的Uri已经存在，就无需再次插入\n             * 否则会出现新插入的文件，文件名被系统更改的现象，因为insert不会执行覆盖操作\n             */\n            if (uri != null) return uri\n            ContentValues().run {\n                put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath) //下载到指定目录\n                put(MediaStore.MediaColumns.DISPLAY_NAME, filename)   //文件名\n                //取contentType响应头作为文件类型\n                put(MediaStore.MediaColumns.MIME_TYPE, response.body?.contentType().toString())\n                context.contentResolver.insert(getInsertUri(), this)\n                //当相同路径下的文件，在文件管理器中被手动删除时，就会插入失败\n            } ?: throw NullPointerException(\"Uri insert failed. Try changing filename\")\n        } else {\n            val file = File(\"${Environment.getExternalStorageDirectory()}/$relativePath/$filename\")\n            Uri.fromFile(file)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/parser/ResponseParser.kt",
    "content": "package com.example.httpsender.parser\n\nimport com.example.httpsender.entity.PageList\nimport com.example.httpsender.entity.Response\nimport rxhttp.wrapper.annotation.Parser\nimport rxhttp.wrapper.exception.ParseException\nimport rxhttp.wrapper.parse.TypeParser\nimport rxhttp.wrapper.utils.convertTo\nimport java.io.IOException\nimport java.lang.reflect.Type\n\n/**\n * 输入T,输出T,并对code统一判断\n * User: ljx\n * Date: 2018/10/23\n * Time: 13:49\n *\n * 如果使用协程发送请求，wrappers属性可不设置，设置了也无效\n */\n@Parser(name = \"Response\", wrappers = [PageList::class])\nopen class ResponseParser<T> : TypeParser<T> {\n    /**\n     * 此构造方法可适用任意Class对象，但更多用于带泛型的Class对象，如：List<List<Student>>>\n     *\n     * 如Java环境中调用\n     * toObservable(new ResponseParser<List<List<Student>>>(){})\n     * 等价于kotlin环境下的\n     * toObservableResponse<List<List<Student>>>()\n     *\n     * 注：此构造方法一定要用protected关键字修饰，否则调用此构造方法将拿不到泛型类型\n     */\n    protected constructor() : super()\n\n    /**\n     * 该解析器会生成以下系列方法，前3个kotlin环境调用，后4个Java环境调用，所有方法内部均会调用本构造方法\n     * toFlowResponse<T>()\n     * toAwaitResponse<T>()\n     * toObservableResponse<T>()\n     * toObservableResponse(Type)\n     * toObservableResponse(Class<T>)\n     * toObservableResponseList(Class<T>)\n     * toObservableResponsePageList(Class<T>)\n     *\n     * Flow/Await下 toXxxResponse<PageList<T>> 等同于 toObservableResponsePageList(Class<T>)\n     */\n    constructor(type: Type) : super(type)\n\n    @Throws(IOException::class)\n    override fun onParse(response: okhttp3.Response): T {\n        val data: Response<T> = response.convertTo(Response::class, *types)\n        var t = data.data //获取data字段\n        if (t == null && types[0] === String::class.java) {\n            /*\n             * 考虑到有些时候服务端会返回：{\"errorCode\":0,\"errorMsg\":\"关注成功\"}  类似没有data的数据\n             * 此时code正确，但是data字段为空，直接返回data的话，会报空指针错误，\n             * 所以，判断泛型为String类型时，重新赋值，并确保赋值不为null\n             */\n            @Suppress(\"UNCHECKED_CAST\")\n            t = data.msg as T\n        }\n        if (data.code != 0 || t == null) { //code不等于0，说明数据不正确，抛出异常\n            throw ParseException(data.code.toString(), data.msg, response)\n        }\n        return t\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/parser/java/DoubleTypeParser.java",
    "content": "package com.example.httpsender.parser.java;\n\nimport android.util.Pair;\n\nimport com.example.httpsender.entity.Response;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\n\nimport rxhttp.wrapper.entity.ParameterizedTypeImpl;\nimport rxhttp.wrapper.exception.ParseException;\nimport rxhttp.wrapper.parse.TypeParser;\nimport rxhttp.wrapper.utils.Converter;\n\n/**\n * 大于等于2个泛型的解析器，可以参考此类\n * User: ljx\n * Date: 2018/10/23\n * Time: 13:49\n */\n//@Parser(name = \"DoubleType\")\npublic class DoubleTypeParser<F, S> extends TypeParser<Pair<F, S>> {\n\n    protected DoubleTypeParser() {\n        super();\n    }\n\n    public DoubleTypeParser(Type fType, Type sType) {\n        super(fType, sType);\n    }\n\n    @Override\n    public Pair<F, S> onParse(@NotNull okhttp3.Response response) throws IOException {\n        Type pairType = ParameterizedTypeImpl.getParameterized(Pair.class, types);\n        Response<Pair<F, S>> data = Converter.convertTo(response, Response.class, pairType);\n        Pair<F, S> t = data.getData(); //获取data字段\n        if (data.getCode() != 0 || t == null) {//code不等于0，说明数据不正确，抛出异常\n            throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);\n        }\n        return t;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/parser/java/ResponseParser.java",
    "content": "package com.example.httpsender.parser.java;\n\nimport com.example.httpsender.entity.Response;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\n\nimport rxhttp.wrapper.exception.ParseException;\nimport rxhttp.wrapper.parse.TypeParser;\nimport rxhttp.wrapper.utils.Converter;\n\n/**\n * 输入T,输出T,并对code统一判断\n * User: ljx\n * Date: 2018/10/23\n * Time: 13:49\n */\n//@Parser(name = \"Response\", wrappers = {PageList.class})\npublic class ResponseParser<T> extends TypeParser<T> {\n\n    /**\n     * 此构造方法可适用任意Class对象，但更多用于带泛型的Class对象，如：List<List<Student>>>\n     * <p>\n     * 如Java环境中调用\n     * toObservable(new ResponseParser<List<List<Student>>>(){})\n     * 等价与kotlin环境下的\n     * toObservableResponse<List<List<Student>>>()\n     * <p>\n     * 注：此构造方法一定要用protected关键字修饰，否则调用此构造方法将拿不到泛型类型\n     */\n    protected ResponseParser() {\n        super();\n    }\n\n    /**\n     * 该解析器会生成以下系列方法，前3个kotlin环境调用，后4个Java环境调用，所有方法内部均会调用本构造方法\n     * toFlowResponse<T>()\n     * toAwaitResponse<T>()\n     * toObservableResponse<T>()\n     * toObservableResponse(Type)\n     * toObservableResponse(Class<T>)\n     * toObservableResponseList(Class<T>)\n     * toObservableResponsePageList(Class<T>)\n     * <p>\n     * Flow/Await下 toXxxResponse<PageList<T>> 等价与 toObservableResponsePageList(Class<T>)\n     */\n    public ResponseParser(Type type) {\n        super(type);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public T onParse(@NotNull okhttp3.Response response) throws IOException {\n        Response<T> data = Converter.convertTo(response, Response.class, types);\n        T t = data.getData(); //获取data字段\n        if (t == null && types[0] == String.class) {\n            /*\n             * 考虑到有些时候服务端会返回：{\"errorCode\":0,\"errorMsg\":\"关注成功\"}  类似没有data的数据\n             * 此时code正确，但是data字段为空，直接返回data的话，会报空指针错误，\n             * 所以，判断泛型为String类型时，重新赋值，并确保赋值不为null\n             */\n            t = (T) data.getMsg();\n        }\n        if (data.getCode() != 0 || t == null) {//code不等于0，说明数据不正确，抛出异常\n            throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);\n        }\n        return t;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/utils/Preferences.java",
    "content": "package com.example.httpsender.utils;\n\nimport android.content.SharedPreferences;\nimport android.preference.PreferenceManager;\n\nimport com.example.httpsender.AppHolder;\n\n\n/**\n * User: hqs\n * Date: 2016/5/10\n * Time: 19:07\n */\npublic class Preferences {\n\n    private static SharedPreferences sharedPreferences;\n    private static SharedPreferences.Editor editor;\n\n    private Preferences() {\n    }\n\n    private static SharedPreferences getInstance() {\n        if (sharedPreferences == null) {\n            synchronized (Preferences.class) {\n                if (sharedPreferences == null) {\n                    sharedPreferences = PreferenceManager.getDefaultSharedPreferences(AppHolder.getInstance());\n                }\n            }\n        }\n        return sharedPreferences;\n    }\n\n    public static SharedPreferences.Editor getEditor() {\n        if (editor == null) {\n            editor = getInstance().edit();\n        }\n        return editor;\n    }\n\n    public static String getValue(String key, String defaultValue) {\n        return getInstance().getString(key, defaultValue);\n    }\n\n    public static void setValue(String key, String value) {\n        getEditor().putString(key, value).commit();\n    }\n\n    public static int getValue(String key, int defaultValue) {\n        return getInstance().getInt(key, defaultValue);\n    }\n\n    public static void setValue(String key, int value) {\n        getEditor().putInt(key, value).commit();\n    }\n\n    public static void setFloat(String key, float value) {\n        getEditor().putFloat(key, value).commit();\n    }\n\n    public static float getFloat(String key, float defaultValue) {\n       return getInstance().getFloat(key, defaultValue);\n    }\n\n    public static boolean getValue(String key, boolean defaultValue) {\n        return getInstance().getBoolean(key, defaultValue);\n    }\n\n    public static void setValue(String key, boolean value) {\n        getEditor().putBoolean(key, value).commit();\n    }\n\n    public static long getValue(String key, long defaultValue) {\n        return getInstance().getLong(key, defaultValue);\n    }\n\n    public static void setValue(String key, long value) {\n        getEditor().putLong(key, value).commit();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/view/ScaleTransitionPagerTitleView.java",
    "content": "package com.example.httpsender.view;\n\nimport android.content.Context;\n\nimport net.lucode.hackware.magicindicator.buildins.commonnavigator.titles.ColorTransitionPagerTitleView;\n\n/**\n * 带颜色渐变和缩放的指示器标题\n * 博客: http://hackware.lucode.net\n * Created by hackware on 2016/6/26.\n */\npublic class ScaleTransitionPagerTitleView extends ColorTransitionPagerTitleView {\n    private float mMinScale = 0.75f;\n\n    public ScaleTransitionPagerTitleView(Context context) {\n        super(context);\n    }\n\n    @Override\n    public void onEnter(int index, int totalCount, float enterPercent, boolean leftToRight) {\n        super.onEnter(index, totalCount, enterPercent, leftToRight);    // 实现颜色渐变\n        setScaleX(mMinScale + (1.0f - mMinScale) * enterPercent);\n        setScaleY(mMinScale + (1.0f - mMinScale) * enterPercent);\n    }\n\n    @Override\n    public void onLeave(int index, int totalCount, float leavePercent, boolean leftToRight) {\n        super.onLeave(index, totalCount, leavePercent, leftToRight);    // 实现颜色渐变\n        setScaleX(1.0f + (mMinScale - 1.0f) * leavePercent);\n        setScaleY(1.0f + (mMinScale - 1.0f) * leavePercent);\n    }\n\n    public float getMinScale() {\n        return mMinScale;\n    }\n\n    public void setMinScale(float minScale) {\n        mMinScale = minScale;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/vm/MultiTaskAwaitDownloader.kt",
    "content": "package com.example.httpsender.vm\n\nimport androidx.lifecycle.MutableLiveData\nimport com.example.httpsender.Tip\nimport com.example.httpsender.entity.DownloadTask\nimport com.example.httpsender.utils.Preferences\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport okhttp3.internal.http2.StreamResetException\nimport okio.ByteString.Companion.encodeUtf8\nimport rxhttp.RxHttpPlugins\nimport rxhttp.awaitResult\nimport rxhttp.wrapper.param.RxHttp\nimport java.io.File\nimport java.util.*\n\n/**\n * User: ljx\n * Date: 2020/7/12\n * Time: 18:00\n */\nobject MultiTaskAwaitDownloader {\n\n    const val IDLE = 0             //未开始，闲置状态\n    const val WAITING = 1          //等待中状态\n    const val DOWNLOADING = 2      //下载中\n    const val PAUSED = 3           //已暂停\n    const val COMPLETED = 4        //已完成\n    const val FAIL = 5             //下载失败\n    const val CANCEL = 6           //取消状态，等待时被取消\n\n    private const val MAX_TASK_COUNT = 3   //最大并发数\n\n    @JvmStatic\n    val liveTask = MutableLiveData<DownloadTask>() //用于刷新UI\n\n    @JvmStatic\n    val allTask = ArrayList<DownloadTask>() //所有下载任务\n    private val waitTask = LinkedList<DownloadTask>() //等待下载的任务\n    private val downloadingTask = LinkedList<DownloadTask>() //下载中的任务\n\n    //记录每个文件的总大小，key为文件url\n    private val lengthMap = HashMap<String, Long>()\n\n    @JvmStatic\n    fun addTasks(tasks: ArrayList<DownloadTask>) {\n        val allTaskList = allTask\n        tasks.forEach {\n            if (!allTaskList.contains(it)) {\n                val md5Key = it.url.encodeUtf8().md5().hex()\n                val length = Preferences.getValue(md5Key, -1L)\n                if (length != -1L) {\n                    it.totalSize = length\n                    it.currentSize = File(it.localPath).length()\n                    it.progress = it.currentSize * 1.0f / it.totalSize\n                    lengthMap[it.url] = length\n\n                    if (it.currentSize > 0) {\n                        it.state = PAUSED\n                    }\n                    if (it.totalSize == it.currentSize) {\n                        //如果当前size等于总size，则任务文件下载完成，注意: 这个判断不是100%准确，最好能对文件做md5校验\n                        it.state = COMPLETED\n                    }\n                }\n                allTaskList.add(it)\n            }\n        }\n    }\n\n    //开始下载所有任务\n    @JvmStatic\n    fun startAllDownloadTask() {\n        val allTaskList = allTask\n        allTaskList.forEach {\n            if (it.state != COMPLETED && it.state != DOWNLOADING) {\n                download(it)\n            }\n        }\n    }\n\n    @JvmStatic\n    fun download(task: DownloadTask) {\n        if (downloadingTask.size >= MAX_TASK_COUNT) {\n            //超过最大下载数，添加进等待队列\n            task.state = WAITING\n            updateTask(task)\n            waitTask.offer(task)\n            return\n        }\n        task.state = DOWNLOADING\n        updateTask(task)\n        downloadingTask.add(task)\n\n        //如果想使用RxJava或Await下载，更改以下代码即可\n        CoroutineScope(Dispatchers.Main).launch {\n//            RxHttp.get(task.url)\n//                .tag(task.url) //记录tag，手动取消下载时用到\n//                .toDownloadAwait(task.localPath, true) {\n//                    //下载进度回调,0-100，仅在进度有更新时才会回调\n//                    task.progress = it.progress        //当前进度 0-100\n//                    task.currentSize = it.currentSize  //当前已下载的字节大小\n//                    task.totalSize = it.totalSize      //要下载的总字节大小\n//                    updateTask(task)\n//                    val key = task.url\n//                    val length = lengthMap[key]\n//                    if (length != task.totalSize) {\n//                        //服务器返回的文件总大小与本地的不一致，则更新\n//                        lengthMap[key] = task.totalSize\n//                        saveTotalSize(lengthMap)\n//                    }\n//                }.awaitResult {\n//                    Tip.show(\"下载成功\")\n//                    task.state = COMPLETED\n//                }.onFailure {\n//                    //手动取消下载时，会收到StreamResetException异常，不做任何处理\n//                    if (it !is StreamResetException) {\n//                        Tip.show(\"下载失败\")\n//                        task.state = FAIL\n//                    }\n//                }\n            //下载结束，不管任务成功还是失败，如果还有在等待的任务，都开启下一个任务\n            updateTask(task)\n            downloadingTask.remove(task)\n            waitTask.poll()?.let { download(it) }\n        }\n    }\n\n\n    private fun saveTotalSize(map: HashMap<String, Long>) {\n        val editor = Preferences.getEditor()\n        for ((key, value) in map) {\n            val md5Key = key.encodeUtf8().md5().hex()\n            editor.putLong(md5Key, value)\n        }\n        editor.commit()\n    }\n\n\n    //关闭所有任务\n    @JvmStatic\n    fun cancelAllTask() {\n        var iterator = waitTask.iterator()\n        while (iterator.hasNext()) {\n            val task = iterator.next()\n            task.state = CANCEL\n            iterator.remove()\n            updateTask(task)\n        }\n\n        iterator = downloadingTask.iterator()\n        while (iterator.hasNext()) {\n            val task = iterator.next()\n            iterator.remove()\n            RxHttpPlugins.cancelAll(task.url)\n            task.state = CANCEL\n            updateTask(task)\n        }\n    }\n\n    //等待中->取消下载\n    @JvmStatic\n    fun removeWaitTask(task: DownloadTask) {\n        waitTask.remove(task)\n        task.state = CANCEL\n        updateTask(task)\n    }\n\n    //暂停下载\n    @JvmStatic\n    fun pauseTask(task: DownloadTask) {\n        //根据tag取消下载\n        RxHttpPlugins.cancelAll(task.url)\n        task.state = PAUSED\n        updateTask(task)\n    }\n\n    @JvmStatic\n    fun haveTaskExecuting(): Boolean {\n        return waitTask.size > 0 || downloadingTask.size > 0\n    }\n\n    //发送通知，更新UI\n    private fun updateTask(task: DownloadTask) {\n        liveTask.value = task\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/vm/MultiTaskDownloader.kt",
    "content": "package com.example.httpsender.vm\n\nimport android.annotation.SuppressLint\nimport androidx.lifecycle.MutableLiveData\nimport com.example.httpsender.Tip\nimport com.example.httpsender.entity.DownloadTask\nimport com.example.httpsender.utils.Preferences\nimport okhttp3.internal.http2.StreamResetException\nimport okio.ByteString.Companion.encodeUtf8\nimport rxhttp.RxHttpPlugins\nimport rxhttp.wrapper.param.RxHttp\nimport java.io.File\nimport java.util.*\n\n/**\n * User: ljx\n * Date: 2020/7/12\n * Time: 18:00\n */\nobject MultiTaskDownloader {\n\n    const val IDLE = 0             //未开始，闲置状态\n    const val WAITING = 1          //等待中状态\n    const val DOWNLOADING = 2      //下载中\n    const val PAUSED = 3           //已暂停\n    const val COMPLETED = 4        //已完成\n    const val FAIL = 5             //下载失败\n    const val CANCEL = 6           //取消状态，等待时被取消\n\n    private const val MAX_TASK_COUNT = 3   //最大并发数\n\n    @JvmStatic\n    val liveTask = MutableLiveData<DownloadTask>() //用于刷新UI\n    @JvmStatic\n    val allTask = ArrayList<DownloadTask>() //所有下载任务\n    private val waitTask = LinkedList<DownloadTask>() //等待下载的任务\n    private val downloadingTask = LinkedList<DownloadTask>() //下载中的任务\n\n    //记录每个文件的总大小，key为文件url\n    private val lengthMap = HashMap<String, Long>()\n\n    @JvmStatic\n    fun addTasks(tasks: ArrayList<DownloadTask>) {\n        val allTaskList = allTask\n        tasks.forEach {\n            if (!allTaskList.contains(it)) {\n                val md5Key = it.url.encodeUtf8().md5().hex()\n                val length = Preferences.getValue(md5Key, -1L)\n                if (length != -1L) {\n                    it.totalSize = length\n                    it.currentSize = File(it.localPath).length()\n                    it.progress = it.currentSize * 1.0f / it.totalSize\n                    lengthMap[it.url] = length\n\n                    if (it.currentSize > 0) {\n                        it.state = PAUSED\n                    }\n                    if (it.totalSize == it.currentSize) {\n                        //如果当前size等于总size，则任务文件下载完成，注意: 这个判断不是100%准确，最好能对文件做md5校验\n                        it.state = COMPLETED\n                    }\n                }\n                allTaskList.add(it)\n            }\n        }\n    }\n\n    //开始下载所有任务\n    @JvmStatic\n    fun startAllDownloadTask() {\n        val allTaskList = allTask\n        allTaskList.forEach {\n            if (it.state != COMPLETED && it.state != DOWNLOADING) {\n                download(it)\n            }\n        }\n    }\n\n    @SuppressLint(\"CheckResult\")\n    @JvmStatic\n    fun download(task: DownloadTask) {\n        if (downloadingTask.size >= MAX_TASK_COUNT) {\n            //超过最大下载数，添加进等待队列\n            task.state = WAITING\n            updateTask(task)\n            waitTask.offer(task)\n            return\n        }\n        task.state = DOWNLOADING\n        updateTask(task)\n        downloadingTask.add(task)\n\n        //如果想使用Await或Flow下载，更改以下代码即可\n        RxHttp.get(task.url)\n            .tag(task.url) //记录tag，手动取消下载时用到\n            .toDownloadObservable(task.localPath, true)\n            .onMainProgress {\n                //下载进度回调,0-100，仅在进度有更新时才会回调\n                task.speed = it.speed\n                task.remainingTime = it.calculateRemainingTime()\n                task.progress = it.fraction        //当前进度 [0.0, 1.0]\n                task.currentSize = it.currentSize  //当前已下载的字节大小\n                task.totalSize = it.totalSize      //要下载的总字节大小\n                updateTask(task)\n                val key = task.url\n                val length = lengthMap[key]\n                if (length != task.totalSize) {\n                    //服务器返回的文件总大小与本地的不一致，则更新\n                    lengthMap[key] = task.totalSize\n                    saveTotalSize(lengthMap)\n                }\n            }\n            .doFinally {\n                updateTask(task)\n                //不管任务成功还是失败，如果还有在等待的任务，都开启下一个任务\n                downloadingTask.remove(task)\n                waitTask.poll()?.let { download(it) }\n            }\n            .subscribe({\n                Tip.show(\"下载完成\")\n                task.state = COMPLETED\n            }, {\n                //手动取消下载时，会收到StreamResetException异常，不做任何处理\n                if (it !is StreamResetException){\n                    Tip.show(\"下载失败\")\n                    task.state = FAIL\n                }\n            })\n\n    }\n\n\n    private fun saveTotalSize(map: HashMap<String, Long>) {\n        val editor = Preferences.getEditor()\n        for ((key, value) in map) {\n            val md5Key = key.encodeUtf8().md5().hex()\n            editor.putLong(md5Key, value)\n        }\n        editor.commit()\n    }\n\n\n    //关闭所有任务\n    @JvmStatic\n    fun cancelAllTask() {\n        var iterator = waitTask.iterator()\n        while (iterator.hasNext()) {\n            val task = iterator.next()\n            task.state = CANCEL\n            iterator.remove()\n            updateTask(task)\n        }\n\n        iterator = downloadingTask.iterator()\n        while (iterator.hasNext()) {\n            val task = iterator.next()\n            iterator.remove()\n            RxHttpPlugins.cancelAll(task.url)\n            task.state = CANCEL\n            updateTask(task)\n        }\n    }\n\n    //等待中->取消下载\n    @JvmStatic\n    fun removeWaitTask(task: DownloadTask) {\n        waitTask.remove(task)\n        task.state = CANCEL\n        updateTask(task)\n    }\n\n    //暂停下载\n    @JvmStatic\n    fun pauseTask(task: DownloadTask) {\n        //根据tag取消下载\n        RxHttpPlugins.cancelAll(task.url)\n        task.state = PAUSED\n        task.speed = 0\n        task.remainingTime = -1\n        updateTask(task)\n    }\n\n    @JvmStatic\n    fun haveTaskExecuting(): Boolean {\n        return waitTask.size > 0 || downloadingTask.size > 0\n    }\n\n    //发送通知，更新UI\n    private fun updateTask(task: DownloadTask) {\n        liveTask.value = task\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/httpsender/vm/MultiTaskFlowDownloader.kt",
    "content": "package com.example.httpsender.vm\n\nimport androidx.lifecycle.MutableLiveData\nimport com.example.httpsender.Tip\nimport com.example.httpsender.entity.DownloadTask\nimport com.example.httpsender.utils.Preferences\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.launch\nimport okhttp3.internal.http2.StreamResetException\nimport okio.ByteString.Companion.encodeUtf8\nimport rxhttp.RxHttpPlugins\nimport rxhttp.toDownloadFlow\nimport rxhttp.wrapper.param.RxHttp\nimport java.io.File\nimport java.util.*\n\n/**\n * User: ljx\n * Date: 2020/7/12\n * Time: 18:00\n */\nobject MultiTaskFlowDownloader {\n\n    const val IDLE = 0             //未开始，闲置状态\n    const val WAITING = 1          //等待中状态\n    const val DOWNLOADING = 2      //下载中\n    const val PAUSED = 3           //已暂停\n    const val COMPLETED = 4        //已完成\n    const val FAIL = 5             //下载失败\n    const val CANCEL = 6           //取消状态，等待时被取消\n\n    private const val MAX_TASK_COUNT = 3   //最大并发数\n\n    @JvmStatic\n    val liveTask = MutableLiveData<DownloadTask>() //用于刷新UI\n\n    @JvmStatic\n    val allTask = ArrayList<DownloadTask>() //所有下载任务\n    private val waitTask = LinkedList<DownloadTask>() //等待下载的任务\n    private val downloadingTask = LinkedList<DownloadTask>() //下载中的任务\n\n    //记录每个文件的总大小，key为文件url\n    private val lengthMap = HashMap<String, Long>()\n\n    @JvmStatic\n    fun addTasks(tasks: ArrayList<DownloadTask>) {\n        val allTaskList = allTask\n        tasks.forEach {\n            if (!allTaskList.contains(it)) {\n                val md5Key = it.url.encodeUtf8().md5().hex()\n                val length = Preferences.getValue(md5Key, -1L)\n                if (length != -1L) {\n                    it.totalSize = length\n                    it.currentSize = File(it.localPath).length()\n                    it.progress = it.currentSize * 1.0f / it.totalSize\n                    lengthMap[it.url] = length\n\n                    if (it.currentSize > 0) {\n                        it.state = PAUSED\n                    }\n                    if (it.totalSize == it.currentSize) {\n                        //如果当前size等于总size，则任务文件下载完成，注意: 这个判断不是100%准确，最好能对文件做md5校验\n                        it.state = COMPLETED\n                    }\n                }\n                allTaskList.add(it)\n            }\n        }\n    }\n\n    //开始下载所有任务\n    @JvmStatic\n    fun startAllDownloadTask() {\n        val allTaskList = allTask\n        allTaskList.forEach {\n            if (it.state != COMPLETED && it.state != DOWNLOADING) {\n                download(it)\n            }\n        }\n    }\n\n    @JvmStatic\n    fun download(task: DownloadTask) {\n        if (downloadingTask.size >= MAX_TASK_COUNT) {\n            //超过最大下载数，添加进等待队列\n            task.state = WAITING\n            updateTask(task)\n            waitTask.offer(task)\n            return\n        }\n\n        task.state = DOWNLOADING\n        updateTask(task)\n        downloadingTask.add(task)\n\n        //如果想使用RxJava或Flow下载，更改以下代码即可\n        CoroutineScope(Dispatchers.Main).launch {\n            RxHttp.get(task.url)\n                .tag(task.url) //记录tag，手动取消下载时用到\n                .toDownloadFlow(task.localPath, true)\n                .onProgress{\n                    //下载进度回调,0-100，仅在进度有更新时才会回调\n                    task.progress = it.fraction        //当前进度 [0.0, 1.0]\n                    task.currentSize = it.currentSize  //当前已下载的字节大小\n                    task.totalSize = it.totalSize      //要下载的总字节大小\n                    updateTask(task)\n                    val key = task.url\n                    val length = lengthMap[key]\n                    if (length != task.totalSize) {\n                        //服务器返回的文件总大小与本地的不一致，则更新\n                        lengthMap[key] = task.totalSize\n                        saveTotalSize(lengthMap)\n                    }\n                }.catch {\n                    //手动取消下载时，会收到StreamResetException异常，不做任何处理\n                    if (it !is StreamResetException) {\n                        Tip.show(\"下载失败\")\n                        task.state = FAIL\n                    }\n                }.collect {\n                    Tip.show(\"下载成功\")\n                    task.state = COMPLETED\n                }\n\n            //下载结束，不管任务成功还是失败，如果还有在等待的任务，都开启下一个任务\n            updateTask(task)\n            downloadingTask.remove(task)\n            waitTask.poll()?.let { download(it) }\n        }\n    }\n\n\n    private fun saveTotalSize(map: HashMap<String, Long>) {\n        val editor = Preferences.getEditor()\n        for ((key, value) in map) {\n            val md5Key = key.encodeUtf8().md5().hex()\n            editor.putLong(md5Key, value)\n        }\n        editor.commit()\n    }\n\n\n    //关闭所有任务\n    @JvmStatic\n    fun cancelAllTask() {\n        var iterator = waitTask.iterator()\n        while (iterator.hasNext()) {\n            val task = iterator.next()\n            task.state = CANCEL\n            iterator.remove()\n            updateTask(task)\n        }\n\n        iterator = downloadingTask.iterator()\n        while (iterator.hasNext()) {\n            val task = iterator.next()\n            iterator.remove()\n            RxHttpPlugins.cancelAll(task.url)\n            task.state = CANCEL\n            updateTask(task)\n        }\n    }\n\n    //等待中->取消下载\n    @JvmStatic\n    fun removeWaitTask(task: DownloadTask) {\n        waitTask.remove(task)\n        task.state = CANCEL\n        updateTask(task)\n    }\n\n    //暂停下载\n    @JvmStatic\n    fun pauseTask(task: DownloadTask) {\n        //根据tag取消下载\n        RxHttpPlugins.cancelAll(task.url)\n        task.state = PAUSED\n        updateTask(task)\n    }\n\n    @JvmStatic\n    fun haveTaskExecuting(): Boolean {\n        return waitTask.size > 0 || downloadingTask.size > 0\n    }\n\n    //发送通知，更新UI\n    private fun updateTask(task: DownloadTask) {\n        liveTask.value = task\n    }\n}"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#008577\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillType=\"evenOdd\"\n        android:pathData=\"M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"78.5885\"\n                android:endY=\"90.9159\"\n                android:startX=\"48.7653\"\n                android:startY=\"61.0927\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/layout/await_fragment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n        <variable\n            name=\"click\"\n            type=\"android.view.View.OnClickListener\" />\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:paddingStart=\"10dp\"\n        android:paddingTop=\"20dp\"\n        android:paddingEnd=\"10dp\"\n        android:paddingBottom=\"20dp\"\n        tools:context=\".MainActivity\">\n\n        <ScrollView\n            android:id=\"@+id/left_scroll_view\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n            <LinearLayout\n                android:id=\"@+id/ll_bt\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center_horizontal\"\n                android:orientation=\"vertical\">\n\n                <Button\n                    android:id=\"@+id/sendGet\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"Get请求\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/sendPostForm\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送表单请求\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/sendPostJson\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送json对象\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/sendPostJsonArray\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送json数组\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/xmlConverter\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送/接收 xml数据\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/upload\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"文件上传\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/upload10\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"Android 10 文件上传\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"文件下载\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download_append\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"文件断点下载\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download10\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"Android 10 文件下载\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download10_append\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"Android 10 断点下载\"\n                    android:textAllCaps=\"false\" />\n\n            </LinearLayout>\n        </ScrollView>\n\n        <ScrollView\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_marginStart=\"10dp\"\n            android:layout_marginBottom=\"10dp\"\n            android:background=\"#CCCCCC\"\n            app:layout_constraintBottom_toTopOf=\"@+id/bt_clear\"\n            app:layout_constraintLeft_toRightOf=\"@+id/left_scroll_view\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"@+id/left_scroll_view\">\n\n            <TextView\n                android:id=\"@+id/tv_result\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"5dp\"\n                android:scrollbars=\"vertical\"\n                android:textColor=\"@android:color/black\" />\n        </ScrollView>\n\n        <Button\n            android:id=\"@+id/bt_clear\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_marginBottom=\"20dp\"\n            android:onClick=\"@{v->click.onClick(v)}\"\n            android:text=\"清空日志\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/download_multi_adapter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"100dp\"\n    android:layout_marginTop=\"1dp\"\n    android:background=\"@android:color/white\"\n    android:gravity=\"center\">\n\n    <TextView\n        android:id=\"@+id/tv_waiting\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"20dp\"\n        android:text=\"等待中...\"\n        app:layout_constraintBottom_toTopOf=\"@+id/progress_bar\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_chainStyle=\"packed\" />\n\n    <ProgressBar\n        android:id=\"@+id/progress_bar\"\n        style=\"?android:attr/progressBarStyleHorizontal\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"20dp\"\n        android:max=\"100\"\n        app:layout_constraintBottom_toTopOf=\"@+id/tv_progress\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/bt_pause\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_waiting\" />\n\n    <TextView\n        android:id=\"@+id/tv_progress\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"20dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/progress_bar\" />\n\n    <TextView\n        android:id=\"@+id/tv_size\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBaseline_toBaselineOf=\"@+id/tv_progress\"\n        app:layout_constraintRight_toRightOf=\"@+id/progress_bar\" />\n\n    <Button\n        android:id=\"@+id/bt_pause\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"20dp\"\n        android:layout_marginRight=\"10dp\"\n        android:text=\"开始\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toRightOf=\"@+id/progress_bar\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/flow_fragment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n        <variable\n            name=\"click\"\n            type=\"android.view.View.OnClickListener\" />\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:paddingStart=\"10dp\"\n        android:paddingTop=\"20dp\"\n        android:paddingEnd=\"10dp\"\n        android:paddingBottom=\"20dp\"\n        tools:context=\".MainActivity\">\n\n        <ScrollView\n            android:id=\"@+id/left_scroll_view\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n            <LinearLayout\n                android:id=\"@+id/ll_bt\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center_horizontal\"\n                android:orientation=\"vertical\">\n\n                <Button\n                    android:id=\"@+id/sendGet\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"Get请求\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/sendPostForm\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送表单请求\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/sendPostJson\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送json对象\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/sendPostJsonArray\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送json数组\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/xmlConverter\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送/接收 xml数据\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/upload\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"文件上传\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/upload10\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"Android 10 文件上传\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"文件下载\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download_append\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"文件断点下载\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download10\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"Android 10 文件下载\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download10_append\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"Android 10 断点下载\"\n                    android:textAllCaps=\"false\" />\n\n            </LinearLayout>\n        </ScrollView>\n\n        <ScrollView\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_marginStart=\"10dp\"\n            android:layout_marginBottom=\"10dp\"\n            android:background=\"#CCCCCC\"\n            app:layout_constraintBottom_toTopOf=\"@+id/bt_clear\"\n            app:layout_constraintLeft_toRightOf=\"@+id/left_scroll_view\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"@+id/left_scroll_view\">\n\n            <TextView\n                android:id=\"@+id/tv_result\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"5dp\"\n                android:scrollbars=\"vertical\"\n                android:textColor=\"@android:color/black\" />\n        </ScrollView>\n\n        <Button\n            android:id=\"@+id/bt_clear\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_marginBottom=\"20dp\"\n            android:onClick=\"@{v->click.onClick(v)}\"\n            android:text=\"清空日志\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/main_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        tools:context=\".MainActivity\">\n\n        <net.lucode.hackware.magicindicator.MagicIndicator\n            android:id=\"@+id/magic_indicator\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"50dp\"\n            android:background=\"@color/colorPrimary\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <androidx.viewpager.widget.ViewPager\n            android:id=\"@+id/view_pager\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/magic_indicator\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/multi_download_fragment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n        <variable\n            name=\"click\"\n            type=\"android.view.View.OnClickListener\" />\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center\"\n        android:orientation=\"vertical\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"#EEEEEE\"\n            android:clipToPadding=\"false\"\n            android:paddingBottom=\"110dp\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n\n        <Button\n            android:id=\"@+id/start_all\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"50dp\"\n            android:onClick=\"@{v->click.onClick(v)}\"\n            android:text=\"全部下载\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintHorizontal_chainStyle=\"packed\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toLeftOf=\"@id/cancel_all\" />\n\n        <Button\n            android:id=\"@+id/cancel_all\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"30dp\"\n            android:onClick=\"@{v->click.onClick(v)}\"\n            android:text=\"全部取消\"\n            app:layout_constraintBottom_toBottomOf=\"@id/start_all\"\n            app:layout_constraintLeft_toRightOf=\"@id/start_all\"\n            app:layout_constraintRight_toRightOf=\"parent\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/rxjava_fragment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n        <variable\n            name=\"click\"\n            type=\"android.view.View.OnClickListener\" />\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:paddingStart=\"10dp\"\n        android:paddingTop=\"20dp\"\n        android:paddingEnd=\"10dp\"\n        android:paddingBottom=\"20dp\"\n        tools:context=\".MainActivity\">\n\n        <ScrollView\n            android:id=\"@+id/left_scroll_view\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n            <LinearLayout\n                android:id=\"@+id/ll_bt\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center_horizontal\"\n                android:orientation=\"vertical\">\n\n                <Button\n                    android:id=\"@+id/sendGet\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"Get请求\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/sendPostForm\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送表单请求\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/sendPostJson\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送json对象\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/sendPostJsonArray\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送json数组\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/xmlConverter\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"发送/接收 xml数据\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/upload\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"文件上传\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/upload10\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"Android 10 文件上传\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"文件下载\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download_append\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"文件断点下载\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download10\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"Android 10 文件下载\"\n                    android:textAllCaps=\"false\" />\n\n                <Button\n                    android:id=\"@+id/download10_append\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{v->click.onClick(v)}\"\n                    android:text=\"Android 10 断点下载\"\n                    android:textAllCaps=\"false\" />\n\n            </LinearLayout>\n        </ScrollView>\n\n        <ScrollView\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_marginStart=\"10dp\"\n            android:layout_marginBottom=\"10dp\"\n            android:background=\"#CCCCCC\"\n            app:layout_constraintBottom_toTopOf=\"@+id/bt_clear\"\n            app:layout_constraintLeft_toRightOf=\"@+id/left_scroll_view\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"@+id/left_scroll_view\">\n\n            <TextView\n                android:id=\"@+id/tv_result\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"5dp\"\n                android:scrollbars=\"vertical\"\n                android:textColor=\"@android:color/black\" />\n        </ScrollView>\n\n        <Button\n            android:id=\"@+id/bt_clear\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"10dp\"\n            android:layout_marginBottom=\"20dp\"\n            android:onClick=\"@{v->click.onClick(v)}\"\n            android:text=\"清空日志\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/toolbar_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:background=\"@color/colorPrimary\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\"\n        app:theme=\"@style/ToolbarTheme\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/menu/download.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/download_all\"\n        android:title=\"全部下载\"\n        app:showAsAction=\"always\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#008577</color>\n    <color name=\"colorPrimaryDark\">#00574B</color>\n    <color name=\"colorAccent\">#D81B60</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">RxHttp</string>\n\n    <string name=\"network_error\">当前无网络，请检查你的网络设置</string>\n    <string name=\"notify_no_network\">网络连接不可用，请稍后重试！</string>\n    <string name=\"esky_service_exception\">网络不给力，请稍候重试！</string>\n    <string name=\"time_out_please_try_again_later\">连接超时,请稍后再试</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimary</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"ToolbarTheme\" parent=\"ThemeOverlay.AppCompat.ActionBar\">\n        <!--没有android:-->\n        <item name=\"actionMenuTextColor\">@android:color/white</item>\n        <item name=\"android:textSize\">16sp</item>\n        <!--只针对ToolBar标题颜色进行设置-->\n        <item name=\"android:textColorPrimary\">@android:color/white</item>\n    </style>\n</resources>"
  },
  {
    "path": "app/src/main/res/xml/network_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <base-config cleartextTrafficPermitted=\"true\" />\n</network-security-config>"
  },
  {
    "path": "app/src/test/java/com/rxhttp/compiler/AbstractTestSymbolProcessor.kt",
    "content": "package com.rxhttp.compiler\n\nimport com.google.devtools.ksp.processing.*\nimport com.google.devtools.ksp.symbol.KSAnnotated\n\n/**\n * Helper class to write tests, only used in Ksp Compile Testing tests, not a public API.\n */\nopen class AbstractTestSymbolProcessor(\n    protected val codeGenerator: CodeGenerator\n) : SymbolProcessor {\n    override fun process(resolver: Resolver): List<KSAnnotated> {\n        return emptyList()\n    }\n}\n\n// Would be nice if SymbolProcessorProvider was a fun interface\ninternal fun processorProviderOf(\n    body: (environment: SymbolProcessorEnvironment) -> SymbolProcessor\n): SymbolProcessorProvider {\n    return object : SymbolProcessorProvider {\n        override fun create(\n            environment: SymbolProcessorEnvironment\n        ): SymbolProcessor {\n            return body(environment)\n        }\n    }\n}"
  },
  {
    "path": "app/src/test/java/com/rxhttp/compiler/KspProcessorTest.kt",
    "content": "package com.rxhttp.compiler\n\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.rxhttp.compiler.ksp.getParserTypeParam\nimport com.tschuchort.compiletesting.KotlinCompilation\nimport com.tschuchort.compiletesting.SourceFile\nimport com.tschuchort.compiletesting.symbolProcessorProviders\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.junit.Assert\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.junit.runners.JUnit4\nimport rxhttp.wrapper.annotation.Parser\nimport java.io.File\n\n/**\n * User: ljx\n * Date: 2023/8/27\n * Time: 17:04\n */\n@RunWith(JUnit4::class)\nclass KspProcessorTest {\n    @OptIn(ExperimentalCompilerApi::class)\n    @Test\n    fun testKspProvider() {\n        val compilation = KotlinCompilation().apply {\n            sources = sourceFiles()\n            symbolProcessorProviders = listOf(KspProvider())\n        }\n        val result = compilation.compile()\n        Assert.assertEquals(result.exitCode, KotlinCompilation.ExitCode.COMPILATION_ERROR)\n    }\n\n\n    @OptIn(ExperimentalCompilerApi::class)\n    @Test\n    fun testKspProvider1() {\n        val compilation = KotlinCompilation().apply {\n            sources = sourceFiles()\n            symbolProcessorProviders = listOf(processorProviderOf {\n                TestSymbolProcessor(it.codeGenerator)\n            })\n        }\n        val result = compilation.compile()\n        Assert.assertEquals(result.exitCode, KotlinCompilation.ExitCode.COMPILATION_ERROR)\n    }\n\n    class TestSymbolProcessor(codeGenerator: CodeGenerator) :\n        AbstractTestSymbolProcessor(codeGenerator) {\n        override fun process(resolver: Resolver): List<KSAnnotated> {\n            resolver.getSymbolsWithAnnotation(Parser::class.java.name).forEach {\n                if (it is KSClassDeclaration) {\n                    val parserTypeParam = it.getParserTypeParam()\n                    println(\"parserTypeParam=$parserTypeParam\")\n                }\n            }\n            return emptyList()\n        }\n    }\n\n    private fun sourceFiles(): List<SourceFile> {\n        val kotlinPath = \"src/main/java/com/example/httpsender/parser\"\n        val entityPath = \"src/main/java/com/example/httpsender/entity\"\n        val annotationPrefix = \"../rxhttp-annotation/src/main/java/rxhttp/wrapper/annotation\"\n        val parserPrefix = \"../rxhttp/src/main/java/rxhttp/wrapper/parse\"\n        val testPrefix = \"src/test/java/com/rxhttp/compiler\"\n        return mutableListOf(\n//            \"$kotlinPath/ResponseParser.kt\",\n            \"$entityPath/Response.java\",\n            \"$parserPrefix/TypeParser.java\",\n            \"$parserPrefix/Parser.java\",\n            \"$parserPrefix/TypeParser.java\",\n            \"$annotationPrefix/Converter.java\",\n            \"$annotationPrefix/DefaultDomain.java\",\n            \"$annotationPrefix/Domain.java\",\n            \"$annotationPrefix/OkClient.java\",\n            \"$annotationPrefix/Param.java\",\n            \"$annotationPrefix/Parser.java\",\n            \"$testPrefix/TestParser1.kt\",\n            \"$testPrefix/TestParser2.kt\",\n            \"src/main/java/com/example/httpsender/entity/User.java\",\n            \"../rxhttp/src/main/java/rxhttp/wrapper/callback/Consumer.java\"\n        ).map { SourceFile.fromPath(File(it)) }\n    }\n\n}"
  },
  {
    "path": "app/src/test/java/com/rxhttp/compiler/TestParser1.kt",
    "content": "package com.rxhttp.compiler\n\nimport rxhttp.wrapper.parse.TypeParser\n\n/**\n * User: ljx\n * Date: 2023/8/28\n * Time: 11:12\n */\nopen class TestParser1<T, F, S> : TypeParser<F> {\n\n    protected constructor() : super()\n    override fun onParse(response: okhttp3.Response): F {\n        TODO(\"Not yet implemented\")\n    }\n}"
  },
  {
    "path": "app/src/test/java/com/rxhttp/compiler/TestParser2.kt",
    "content": "package com.rxhttp.compiler\n\nimport com.example.httpsender.entity.Response\nimport com.example.httpsender.entity.User\nimport rxhttp.wrapper.annotation.Parser\nimport rxhttp.wrapper.callback.Consumer\nimport java.lang.reflect.Type\n\n/**\n * User: ljx\n * Date: 2023/8/28\n * Time: 11:12\n */\n@Parser(name = \"test\")\nclass TestParser2<A, B, C>: TestParser1<A, Response<B?>?, C>,\n    rxhttp.wrapper.parse.Parser<Response<B?>?> {\n\n    protected constructor() : super()\n\n    constructor(typeT: Type, typeF: Type, typeS: Type)\n\n    override fun onParse(response: okhttp3.Response): Response<B?> {\n        TODO(\"Not yet implemented\")\n//        return null\n//        return super.onParse(response)\n    }\n\n//    override fun accept(t: User) {\n//        TODO(\"Not yet implemented\")\n//    }\n\n}"
  },
  {
    "path": "app/src/test/java/com.example.httpsender/AsyncTest.java",
    "content": "package com.example.httpsender;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static okhttp3.mockwebserver.SocketPolicy.DISCONNECT_AFTER_REQUEST;\n\nimport com.example.httpsender.entity.Url;\nimport com.example.httpsender.entity.User;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport io.reactivex.rxjava3.observers.TestObserver;\nimport okhttp3.mockwebserver.MockResponse;\nimport okhttp3.mockwebserver.MockWebServer;\nimport okhttp3.mockwebserver.RecordedRequest;\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.wrapper.annotation.DefaultDomain;\nimport rxhttp.wrapper.entity.OkResponse;\nimport rxhttp.wrapper.entity.ParameterizedTypeImpl;\nimport rxhttp.wrapper.param.RxHttp;\n\n/**\n * User: ljx\n * Date: 2020/6/20\n * Time: 09:14\n */\npublic class AsyncTest {\n\n    @DefaultDomain\n    public static String baseUrl = \"\";\n    MockWebServer server = new MockWebServer();\n\n    @Before\n    public void setUp() throws Exception {\n        RxHttpPlugins.init(null).setDebug(true);\n        baseUrl = server.url(\"\").toString();\n        System.out.println(\"baseUrl=\"+baseUrl);\n        Url.baseUrl = baseUrl;\n    }\n\n    @Test\n    public void success() throws InterruptedException {\n        TestObserver<String> observer = new TestObserver<>();\n        RxHttp.postForm(\"/test\")\n            .toObservableString()\n            .subscribe(observer);\n        assertFalse(observer.await(1, SECONDS));  //等待1s后返回数据\n        server.enqueue(new MockResponse()\n            .setBody(\"{\\\"code\\\":100}\")\n            .throttleBody(1, 100, TimeUnit.MILLISECONDS)  //模拟网速慢，每100毫秒传递1个字节\n        );\n        assertTrue(observer.await(2, SECONDS)); //等待读取Body\n        observer.assertComplete();\n\n        RecordedRequest request = server.takeRequest();\n        assertEquals(\"/test\", request.getPath());\n        assertEquals(\"POST\", request.getMethod());\n    }\n\n    @Test\n    public void successList() throws InterruptedException {\n        TestObserver<List<Integer>> observer = new TestObserver<>();\n        RxHttp.postForm(\"/test\")\n            .toObservableList(int.class)\n            .subscribe(observer);\n        assertFalse(observer.await(1, SECONDS));  //等待1s后返回数据\n        server.enqueue(new MockResponse()\n            .setBody(\"[1,2,3,4]\")\n            .throttleBody(1, 100, TimeUnit.MILLISECONDS)  //模拟网速慢，每100毫秒传递1个字节\n        );\n        assertTrue(observer.await(2, SECONDS)); //等待读取Body\n        observer.assertComplete();\n    }\n\n    @Test\n    public void successOKResponse() throws InterruptedException {\n        Type type = ParameterizedTypeImpl.get(OkResponse.class, User.class);\n        TestObserver<OkResponse<String>> observer = new TestObserver<>();\n        RxHttp.postForm(\"/test\")\n            .<OkResponse<String>>toObservable(type)\n            .subscribe(observer);\n        assertFalse(observer.await(1, SECONDS));  //等待1s后返回数据\n        server.enqueue(new MockResponse()\n            .setBody(\"{\\\"token\\\":\\\"abcdefg\\\"}\")\n            .throttleBody(1, 100, TimeUnit.MILLISECONDS)  //模拟网速慢，每100毫秒传递1个字节\n        );\n        assertTrue(observer.await(2, SECONDS)); //等待读取Body\n        observer.assertComplete();\n    }\n\n\n    @Test\n    public void failure() throws InterruptedException {\n        TestObserver<String> observer = new TestObserver<>();\n        RxHttp.postForm(\"/test\")\n            .toObservableString()\n            .subscribe(observer);\n        assertFalse(observer.await(1, SECONDS));\n        server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AFTER_REQUEST));\n        observer.await(1, SECONDS);\n        observer.assertError(IOException.class);\n    }\n}\n"
  },
  {
    "path": "build.gradle",
    "content": "plugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.kotlin.android) apply false\n}"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nagp = \"9.0.0\"\nkotlin = \"2.3.10\"\nandroid = \"4.1.1.4\"\nappcompat = \"1.7.1\"\nconstraintlayout = \"2.2.1\"\nfastjson = \"1.2.76\"\nfragment = \"1.8.9\"\ngson = \"2.13.2\"\njacksonCore = \"2.19.2\"\njavapoet = \"1.13.0\"\njunit = \"4.13.2\"\njunitVersion = \"1.3.0\"\nespressoCore = \"3.7.0\"\nkotlinCompileTestingKsp = \"1.6.0\"\nkotlinpoet = \"2.2.0\"\nkotlinxCoroutinesAndroid = \"1.10.2\"\nkotlinxSerializationJson = \"1.10.0\"\nlifecycleRuntime = \"2.10.0\"\nmagicindicator = \"1.7.0\"\nmoshi = \"1.15.2\"\nmultidex = \"2.0.1\"\nokhttp = \"5.3.2\"\nprotobufJava = \"4.33.5\"\nrecyclerview = \"1.4.0\"\nrxandroid = \"3.0.2\"\nrxhttp = \"3.5.1\"\nrxjava = \"3.1.12\"\nrxlifeRxjava3 = \"2.2.2\"\nsimpleXml = \"2.7.1\"\nutilcodex = \"1.31.1\"\nksp = \"2.3.5\"\n\n[libraries]\nandroid = { module = \"com.google.android:android\", version.ref = \"android\" }\nandroidx-appcompat = { module = \"androidx.appcompat:appcompat\", version.ref = \"appcompat\" }\nandroidx-constraintlayout = { module = \"androidx.constraintlayout:constraintlayout\", version.ref = \"constraintlayout\" }\nandroidx-espresso-core = { module = \"androidx.test.espresso:espresso-core\", version.ref = \"espressoCore\" }\nandroidx-fragment-ktx = { module = \"androidx.fragment:fragment-ktx\", version.ref = \"fragment\" }\nandroidx-junit = { module = \"androidx.test.ext:junit\", version.ref = \"junitVersion\" }\nandroidx-lifecycle-livedata-ktx = { module = \"androidx.lifecycle:lifecycle-livedata-ktx\", version.ref = \"lifecycleRuntime\" }\nandroidx-lifecycle-runtime-ktx = { module = \"androidx.lifecycle:lifecycle-runtime-ktx\", version.ref = \"lifecycleRuntime\" }\nandroidx-lifecycle-service = { module = \"androidx.lifecycle:lifecycle-service\", version.ref = \"lifecycleRuntime\" }\nandroidx-lifecycle-viewmodel-ktx = { module = \"androidx.lifecycle:lifecycle-viewmodel-ktx\", version.ref = \"lifecycleRuntime\" }\nandroidx-multidex = { module = \"androidx.multidex:multidex\", version.ref = \"multidex\" }\nandroidx-recyclerview = { module = \"androidx.recyclerview:recyclerview\", version.ref = \"recyclerview\" }\nrxhttp = { module = \"com.github.liujingxing.rxhttp:rxhttp\", version.ref = \"rxhttp\" }\nrxhttp-compiler = { module = \"com.github.liujingxing.rxhttp:rxhttp-compiler\", version.ref = \"rxhttp\" }\nrxhttp-annotation = { module = \"com.github.liujingxing.rxhttp:rxhttp-annotation\", version.ref = \"rxhttp\" }\nrxhttp-converter-fastjson = { module = \"com.github.liujingxing.rxhttp:converter-fastjson\", version.ref = \"rxhttp\" }\nrxhttp-converter-jackson = { module = \"com.github.liujingxing.rxhttp:converter-jackson\", version.ref = \"rxhttp\" }\nrxhttp-converter-moshi = { module = \"com.github.liujingxing.rxhttp:converter-moshi\", version.ref = \"rxhttp\" }\nrxhttp-converter-protobuf = { module = \"com.github.liujingxing.rxhttp:converter-protobuf\", version.ref = \"rxhttp\" }\nrxhttp-converter-serialization = { module = \"com.github.liujingxing.rxhttp:converter-serialization\", version.ref = \"rxhttp\" }\nrxhttp-converter-simplexml = { module = \"com.github.liujingxing.rxhttp:converter-simplexml\", version.ref = \"rxhttp\" }\nfastjson = { module = \"com.alibaba:fastjson\", version.ref = \"fastjson\" }\ngson = { module = \"com.google.code.gson:gson\", version.ref = \"gson\" }\njackson-annotations = { module = \"com.fasterxml.jackson.core:jackson-annotations\", version.ref = \"jacksonCore\" }\njackson-core = { module = \"com.fasterxml.jackson.core:jackson-core\", version.ref = \"jacksonCore\" }\njackson-databind = { module = \"com.fasterxml.jackson.core:jackson-databind\", version.ref = \"jacksonCore\" }\njavapoet = { module = \"com.squareup:javapoet\", version.ref = \"javapoet\" }\njunit = { module = \"junit:junit\", version.ref = \"junit\" }\nkotlin-compile-testing-ksp = { module = \"com.github.tschuchortdev:kotlin-compile-testing-ksp\", version.ref = \"kotlinCompileTestingKsp\" }\nkotlinpoet = { module = \"com.squareup:kotlinpoet\", version.ref = \"kotlinpoet\" }\nkotlinpoet-javapoet = { module = \"com.squareup:kotlinpoet-javapoet\", version.ref = \"kotlinpoet\" }\nkotlinpoet-ksp = { module = \"com.squareup:kotlinpoet-ksp\", version.ref = \"kotlinpoet\" }\nkotlinx-coroutines-android = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-android\", version.ref = \"kotlinxCoroutinesAndroid\" }\nkotlinx-serialization-json = { module = \"org.jetbrains.kotlinx:kotlinx-serialization-json\", version.ref = \"kotlinxSerializationJson\" }\ngithub-magicindicator = { module = \"com.github.hackware1993:MagicIndicator\", version.ref = \"magicindicator\" }\nmockwebserver = { module = \"com.squareup.okhttp3:mockwebserver\", version.ref = \"okhttp\" }\nmoshi = { module = \"com.squareup.moshi:moshi\", version.ref = \"moshi\" }\nokhttp = { module = \"com.squareup.okhttp3:okhttp\", version.ref = \"okhttp\" }\nprotobuf-java = { module = \"com.google.protobuf:protobuf-java\", version.ref = \"protobufJava\" }\nrxandroid = { module = \"io.reactivex.rxjava3:rxandroid\", version.ref = \"rxandroid\" }\n\nrxjava = { module = \"io.reactivex.rxjava3:rxjava\", version.ref = \"rxjava\" }\ngithub-rxlife-rxjava3 = { module = \"com.github.liujingxing.rxlife:rxlife-rxjava3\", version.ref = \"rxlifeRxjava3\" }\nsimple-xml = { module = \"org.simpleframework:simple-xml\", version.ref = \"simpleXml\" }\nsymbol-processing-api = { module = \"com.google.devtools.ksp:symbol-processing-api\", version.ref = \"ksp\" }\nutilcodex = { module = \"com.blankj:utilcodex\", version.ref = \"utilcodex\" }\n\n\n[plugins]\nandroid-application = { id = \"com.android.application\", version.ref = \"agp\" }\nkotlin-android = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\n\n\n\n\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue Oct 13 10:34:15 CST 2020\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.1-bin.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\nandroid.enableJetifier=true\nandroid.useAndroidX=true\norg.gradle.jvmargs=-Xmx1536m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n\n#org.gradle.daemon=true\n#org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005\n\njdk_version=8\nandroid.defaults.buildfeatures.resvalues=true\nandroid.sdk.defaultTargetSdkToCompileSdkIfUnset=false\nandroid.enableAppCompileTimeRClass=false\nandroid.usesSdkInManifest.disallowed=false\nandroid.uniquePackageNames=false\nandroid.dependency.useConstraints=true\nandroid.r8.strictFullModeForKeepRules=false\nandroid.r8.optimizedResourceShrinking=false\nandroid.builtInKotlin=false\nandroid.newDsl=false"
  },
  {
    "path": "jitpack.yml",
    "content": "jdk:\n  - openjdk17\n"
  },
  {
    "path": "maven.gradle",
    "content": "apply plugin: 'maven-publish'\n\npublishing {\n    publications {\n        release(MavenPublication) {\n            from components.java\n        }\n    }\n}"
  },
  {
    "path": "maven_dependency.md",
    "content": "## 注意\n\n- RxHttp支持`RxJava2和RxJava3`，依赖时需要选择对应的注解处理器，如不需要支持RxJava，则可不选择注解处理器\n\n```xml\n<dependencies>\n\n    <!--必须-->\n    <dependency>\n        <groupId>com.squareup.okhttp3</groupId>\n        <artifactId>okhttp</artifactId>\n        <version>4.12.0</version>\n    </dependency>\n\n    <!--必须-->\n    <dependency>\n        <groupId>com.github.liujingxing.rxhttp</groupId>\n        <artifactId>rxhttp</artifactId>\n        <version>3.5.1</version>\n    </dependency>\n\n    <!-- 非必须 RxJava2/RxJava3 二选一或都不选 -->\n    <!-- <dependency>\n        <groupId>io.reactivex.rxjava2</groupId>\n        <artifactId>rxjava</artifactId>\n        <version>2.2.8</version>\n    </dependency> -->\n\n    <dependency>\n        <groupId>io.reactivex.rxjava3</groupId>\n        <artifactId>rxjava</artifactId>\n        <version>3.1.5</version>\n    </dependency>\n\n</dependencies>\n\n<build>\n    <plugins>\n        <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-compiler-plugin</artifactId>\n            <version>3.8.1</version>\n            <configuration>\n                <source>1.8</source>\n                <target>1.8</target>\n                <!--必须-->\n                <annotationProcessorPaths>\n                    <path>\n                        <groupId>com.github.liujingxing.rxhttp</groupId>\n                        <artifactId>rxhttp-compiler</artifactId>\n                        <version>3.5.1</version>\n                    </path>\n                </annotationProcessorPaths>\n\n                <!--RxJava注解处理器，非必须-->\n                <annotationProcessors>\n                    <!--以下两个注解处理器，分别对应RxJava2/RxJava3，二选一即可 -->\n                    <!-- <annotationProcessor>\n                        com.rxhttp.compiler.maven.AnnotationRxJava2Processor\n                    </annotationProcessor> -->\n                    <annotationProcessor>\n                        com.rxhttp.compiler.maven.AnnotationRxJava3Processor\n                    </annotationProcessor>\n                </annotationProcessors>\n            </configuration>\n        </plugin>\n    </plugins>\n</build>\n```\n"
  },
  {
    "path": "rxhttp/build.gradle",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    id 'java-library'\n    id 'org.jetbrains.kotlin.jvm'\n}\napply from: '../maven.gradle'\n\nsourceSets {\n    main.java.srcDirs += \"$buildDir/generated/sources/java-templates/java/main\"\n}\n\ncompileKotlin {\n    dependsOn 'copyJavaTemplates'\n}\n\ntasks.register('copyJavaTemplates', Copy) {\n    from 'src/main/java-templates'\n    into \"${layout.buildDirectory.get()}/generated/sources/java-templates/java/main\"\n    expand('projectVersion': libs.versions.rxhttp.get())\n    filteringCharset = 'UTF-8'\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    compileOnly libs.android\n    compileOnly libs.okhttp\n//    api libs.rxhttp.annotation\n    api projects.rxhttpAnnotation\n    api libs.gson\n    api libs.kotlinx.coroutines.android\n    testImplementation libs.junit\n}\n\njava {\n    sourceCompatibility = \"$jdk_version\"\n    targetCompatibility = \"$jdk_version\"\n}\nkotlin {\n    compilerOptions {\n        jvmTarget = JvmTarget.JVM_1_8\n    }\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/AwaitTransform.kt",
    "content": "package rxhttp\n\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Deferred\nimport kotlinx.coroutines.InternalCoroutinesApi\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.withContext\nimport kotlinx.coroutines.withTimeout\nimport rxhttp.wrapper.coroutines.Await\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\n\n/**\n * User: ljx\n * Date: 2021/9/18\n * Time: 17:56\n */\nprivate class SafeAwait<T>(private val block: suspend () -> T) : Await<T> {\n    override suspend fun await(): T = block()\n}\n\nfun <T> newAwait(block: suspend () -> T): Await<T> = SafeAwait(block)\n\n/**\n * @param times  retry times, default Long.MAX_VALUE Always try again\n * @param period retry period, default 0, time in milliseconds\n * @param test   retry conditions, default true，Unconditional retry\n */\nfun <T> Await<T>.retry(\n    times: Long = Long.MAX_VALUE,\n    period: Long = 0,\n    test: suspend (Throwable) -> Boolean = { true }\n): Await<T> = object : Await<T> {\n\n    var retryTime = times\n\n    override suspend fun await(): T {\n        return try {\n            this@retry.await()\n        } catch (e: Throwable) {\n            e.throwCancellationCause(currentCoroutineContext())\n            val remaining = retryTime  //Remaining retries\n            if (remaining != Long.MAX_VALUE) {\n                retryTime = remaining - 1\n            }\n            val pass = test(e)\n            if (remaining > 0 && pass) {\n                kotlinx.coroutines.delay(period)\n                await()\n            } else throw e\n        }\n    }\n}\n\nfun <T> Await<T>.onStart(\n    action: suspend () -> Unit\n): Await<T> = newAwait {\n    action()\n    await()\n}\n\n/**\n * @param times  repeat times, default Long.MAX_VALUE Always repeat\n * @param period repeat period, default 0, time in milliseconds\n * @param stop   repeat stop conditions, default false，Unconditional repeat\n */\nfun <T> Await<T>.repeat(\n    times: Long = Long.MAX_VALUE,\n    period: Long = 0,\n    stop: suspend (T) -> Boolean = { false }\n): Await<T> = object : Await<T> {\n\n    var remaining = if (times == Long.MAX_VALUE) Long.MAX_VALUE else times - 1\n\n    override suspend fun await(): T {\n        while (remaining > 0) {\n            if (remaining != Long.MAX_VALUE) {\n                remaining--\n            }\n            val t = this@repeat.await()\n            if (stop(t)) {\n                return t\n            }\n            kotlinx.coroutines.delay(period)\n        }\n        return this@repeat.await()\n    }\n}\n\n/**\n * Changes the context where this flow is executed to the given [context].\n * This operator is composable and affects only preceding operators that do not have its own context.\n * This operator is context preserving: [context] **does not** leak into the downstream flow.\n *\n * For example:\n *\n * ```\n * lifecycleScope.launch {\n *     val t = RxHttp.get(\"...\")\n *         .toAwait<T>()\n *         .map { ... }    // Will be executed in IO\n *         .flowOn(Dispatchers.IO)\n *         .map { ... }    // Will be executed in Default\n *         .flowOn(Dispatchers.Default)\n *         .flowOn(Dispatchers.IO)\n *         .map { ... }    // Will be executed in the Main\n *         .await()        // Will be executed in the Main\n * }\n * ```\n */\nfun <T> Await<T>.flowOn(\n    context: CoroutineContext\n): Await<T> = newAwait {\n    withContext(context) { await() }\n}\n\n/**\n * Creates a flow that produces values from the given Await.\n */\nfun <T> Await<T>.asFlow(): Flow<T> = flow {\n    emit(await())\n}\n\n/**\n * Set the timeout for the request\n * @param timeMillis timeout time in milliseconds.\n *\n * timeMillis should be less than the sum of (connection + read + write) durations, otherwise invalid\n */\nfun <T> Await<T>.timeout(\n    timeMillis: Long\n): Await<T> = newAwait {\n    withTimeout(timeMillis) { await() }\n}\n\n/**\n * Returns a Await containing the specified object when an error occurs.\n */\nfun <T> Await<T>.onErrorReturnItem(t: T): Await<T> = onErrorReturn { t }\n\n/**\n * Returns a Await containing the object specified by the [map] function when an error occurs.\n */\ninline fun <T> Await<T>.onErrorReturn(\n    crossinline map: suspend (Throwable) -> T\n): Await<T> = newAwait {\n    try {\n        await()\n    } catch (e: Throwable) {\n        e.throwCancellationCause(currentCoroutineContext())\n        map(e)\n    }\n}\n\n/**\n * Returns a Await containing the results of applying the given [map] function\n */\ninline fun <T, R> Await<T>.map(\n    crossinline map: suspend (T) -> R\n): Await<R> = newAwait {\n    map(await())\n}\n\ninline fun <T> Await<T>.onEach(\n    crossinline each: suspend (T) -> Unit\n): Await<T> = newAwait {\n    await().also { each(it) }\n}\n\n/**\n * Delay return by [timeMillis] millisecond after await.\n *\n * @param timeMillis time in milliseconds.\n */\nfun <T> Await<T>.delay(timeMillis: Long): Await<T> = newAwait {\n    await().also { kotlinx.coroutines.delay(timeMillis) }\n}\n\n/**\n * Delay return by [timeMillis] millisecond before await.\n *\n * @param timeMillis time in milliseconds.\n */\nfun <T> Await<T>.startDelay(timeMillis: Long): Await<T> = newAwait {\n    kotlinx.coroutines.delay(timeMillis)\n    await()\n}\n\n/**\n * 为修复 https://github.com/liujingxing/rxhttp/issues/533 问题，请使用supervisorScope + safeAsync方法替换\n *\n * For example:\n *\n * ```\n * lifecycleScope.launch {\n *     supervisorScope {\n *         val deferred1 = RxHttp.get(\"...\")\n *             .safeAsync(this)\n *         val deferred2 = RxHttp.get(\"...\")\n *             .safeAsync(this)\n *     }\n * }\n * ```\n */\n@Deprecated(message = \"Use [Await.safeAsync] instead\")\nfun <T> Await<T>.async(\n    scope: CoroutineScope,\n    context: CoroutineContext = SupervisorJob(scope.coroutineContext[Job]),\n    start: CoroutineStart = CoroutineStart.DEFAULT\n): Deferred<T> = safeAsync(scope, context, start)\n\n/**\n * Creates a coroutine and returns its future result as an implementation of [Deferred].\n */\nfun <T> Await<T>.safeAsync(\n    scope: CoroutineScope,\n    context: CoroutineContext = EmptyCoroutineContext,\n    start: CoroutineStart = CoroutineStart.DEFAULT,\n): Deferred<T> = scope.async(context, start) {\n    await()\n}\n\nsuspend inline fun <T> Await<T>.awaitResult(): Result<T> = resultCatching { await() }\n\nsuspend inline fun <T> Await<T>.awaitResult(onSuccess: (T) -> Unit): Result<T> =\n    awaitResult().onSuccess(onSuccess)\n\nsuspend inline fun <T> Deferred<T>.awaitResult(): Result<T> = resultCatching { await() }\n\nsuspend inline fun <T> Deferred<T>.awaitResult(onSuccess: (T) -> Unit): Result<T> =\n    awaitResult().onSuccess(onSuccess)\n\n//return null when an error occurs.\nsuspend fun <T> Deferred<T>.tryAwait(onCatch: ((Throwable) -> Unit)? = null): T? =\n    try {\n        await()\n    } catch (e: Throwable) {\n        e.throwCancellationCause(currentCoroutineContext())\n        onCatch?.invoke(e)\n        null\n    }\n\n//return null when an error occurs.\nsuspend fun <T> Await<T>.tryAwait(onCatch: ((Throwable) -> Unit)? = null): T? =\n    try {\n        await()\n    } catch (e: Throwable) {\n        e.throwCancellationCause(currentCoroutineContext())\n        onCatch?.invoke(e)\n        null\n    }\n\n//return default value when an error occurs.\nsuspend inline fun <T> Deferred<T>.safeAwait(onCatch: (Throwable) -> T): T =\n    try {\n        await()\n    } catch (e: Throwable) {\n        e.throwCancellationCause(currentCoroutineContext())\n        onCatch(e)\n    }\n\n//return default value when an error occurs.\nsuspend inline fun <T> Await<T>.safeAwait(onCatch: (Throwable) -> T): T =\n    try {\n        await()\n    } catch (e: Throwable) {\n        e.throwCancellationCause(currentCoroutineContext())\n        onCatch(e)\n    }\n\nsuspend inline fun <T, R> T.resultCatching(block: T.() -> R): Result<R> {\n    return try {\n        Result.success(block())\n    } catch (e: Throwable) {\n        e.throwCancellationCause(currentCoroutineContext())\n        Result.failure(e)\n    }\n}\n\nfun Throwable.throwCancellationCause(coroutineContext: CoroutineContext) {\n    if (isCancellationCause(coroutineContext)) throw this\n}\n\n@OptIn(InternalCoroutinesApi::class)\ninternal fun Throwable.isCancellationCause(coroutineContext: CoroutineContext): Boolean {\n    val job = coroutineContext[Job]\n    if (job == null || !job.isCancelled) return false\n    return this == job.getCancellationException()\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/CallFactoryExt.kt",
    "content": "package rxhttp\n\nimport android.content.Context\nimport android.net.Uri\nimport rxhttp.wrapper.CallFactory\nimport rxhttp.wrapper.ITag\nimport rxhttp.wrapper.callback.FileOutputStreamFactory\nimport rxhttp.wrapper.callback.OutputStreamFactory\nimport rxhttp.wrapper.callback.UriOutputStreamFactory\nimport rxhttp.wrapper.coroutines.Await\nimport rxhttp.wrapper.coroutines.CallAwait\nimport rxhttp.wrapper.coroutines.CallFlow\nimport rxhttp.wrapper.parse.Parser\nimport rxhttp.wrapper.parse.SmartParser\nimport rxhttp.wrapper.parse.StreamParser\nimport rxhttp.wrapper.utils.javaTypeOf\n\n/**\n * User: ljx\n * Date: 2021/9/18\n * Time: 17:34\n */\nfun <T> CallFactory.toAwait(parser: Parser<T>): CallAwait<T> = CallAwait(this, parser)\n\ninline fun <reified T> CallFactory.toAwait(): CallAwait<T> = toAwait(SmartParser.wrap(javaTypeOf<T>()))\n\nfun CallFactory.toAwaitString(): CallAwait<String> = toAwait()\n\ninline fun <reified T> CallFactory.toAwaitList(): CallAwait<MutableList<T>> = toAwait()\n\nfun CallFactory.toDownloadAwait(\n    destPath: String,\n    append: Boolean = false,\n): Await<String> = toDownloadAwait(FileOutputStreamFactory(destPath), append)\n\nfun CallFactory.toDownloadAwait(\n    context: Context,\n    uri: Uri,\n    append: Boolean = false,\n): Await<Uri> = toDownloadAwait(UriOutputStreamFactory(context, uri), append)\n\nfun <T> CallFactory.toDownloadAwait(\n    osFactory: OutputStreamFactory<T>,\n    append: Boolean = false,\n): Await<T> {\n    if (append && this is ITag) {\n        tag(OutputStreamFactory::class.java, osFactory)\n    }\n    return toAwait(StreamParser(osFactory))\n}\n\n\nfun <T> CallFactory.toFlow(parser: Parser<T>): CallFlow<T> = CallFlow(this, parser)\n\ninline fun <reified T> CallFactory.toFlow(): CallFlow<T> = toFlow(SmartParser.wrap(javaTypeOf<T>()))\n\nfun CallFactory.toFlowString(): CallFlow<String> = toFlow()\n\ninline fun <reified T> CallFactory.toFlowList(): CallFlow<List<T>> = toFlow()\n\n/**\n * @param destPath Local storage path\n * @param append is append download\n */\nfun CallFactory.toDownloadFlow(\n    destPath: String,\n    append: Boolean = false,\n): CallFlow<String> = toDownloadFlow(FileOutputStreamFactory(destPath), append)\n\nfun CallFactory.toDownloadFlow(\n    context: Context,\n    uri: Uri,\n    append: Boolean = false,\n): CallFlow<Uri> = toDownloadFlow(UriOutputStreamFactory(context, uri), append)\n\nfun <T> CallFactory.toDownloadFlow(\n    osFactory: OutputStreamFactory<T>,\n    append: Boolean = false,\n): CallFlow<T> {\n    if (append && this is ITag) {\n        tag(OutputStreamFactory::class.java, osFactory)\n    }\n    return toFlow(StreamParser(osFactory))\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/Platform.java",
    "content": "package rxhttp;\n\nimport android.os.Build;\nimport android.util.Log;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\n\nimport rxhttp.wrapper.utils.LogUtil;\n\n/**\n * User: ljx\n * Date: 2019-12-29\n * Time: 16:07\n */\npublic class Platform {\n    private static final Platform PLATFORM = findPlatform();\n\n    public static Platform get() {\n        return PLATFORM;\n    }\n\n    private Platform() {\n    }\n\n    private static Platform findPlatform() {\n        try {\n            Class.forName(\"android.os.Build\");\n            if (Build.VERSION.SDK_INT != 0) {\n                return new Android();\n            }\n        } catch (ClassNotFoundException ignored) {\n        }\n        return new Platform();\n    }\n\n    //是否Android平台\n    public boolean isAndroid() {\n        return false;\n    }\n\n    public boolean sdkLessThan(int api) {\n        return false;\n    }\n\n    public void logi(String tag, String message) {\n        System.out.println(tag + \": \" + message);\n    }\n\n    public void logd(String tag, String message) {\n        System.out.println(tag + \": \" + message);\n    }\n\n    public void loge(String tag, String message) {\n        System.out.println(tag + \": \" + message);\n    }\n\n    public void loge(String tag, String message, Throwable e) {\n        System.err.println(tag + \": \" + message);\n        e.printStackTrace();\n    }\n\n    public void loge(String tag, Throwable e) {\n        e.printStackTrace();\n    }\n\n    static final class Android extends Platform {\n        Android() {\n            super();\n        }\n\n        @Override\n        public boolean isAndroid() {\n            return true;\n        }\n\n        @Override\n        public boolean sdkLessThan(int api) {\n            return Build.VERSION.SDK_INT < api;\n        }\n\n        @Override\n        public void logi(String tag, String message) {\n            log(Log.INFO, tag, message);\n        }\n\n        @Override\n        public void logd(String tag, String message) {\n            log(Log.DEBUG, tag, message);\n        }\n\n        @Override\n        public void loge(String tag, String message) {\n            log(Log.ERROR, tag, message);\n        }\n\n        @Override\n        public void loge(String tag, String message, Throwable e) {\n            log(Log.ERROR, tag, message);\n            printThrowable(e, tag);\n        }\n\n        @Override\n        public void loge(String tag, Throwable e) {\n            printThrowable(e, tag);\n        }\n\n        private void log(int priority, String tag, String content) {\n            int p = 3 * 1024;\n            int i = 0;\n            while (content.getBytes().length > p) {\n                String logContent = new String(content.getBytes(), 0, p);\n                //最后一个字符有可能是乱码(中文被截取一个字节等原因)，故移除\n                logContent = logContent.substring(0, logContent.length() - 1);\n                Log.println(priority, tag, logContent);\n                if (!LogUtil.isSegmentPrint()) return;\n                Log.v(tag, \"<---------------------------------- Segment \" + (++i) + \" ---------------------------------->\");\n                content = content.substring(logContent.length());\n            }\n            if (content.length() > 0)\n                Log.println(priority, tag, content);\n        }\n\n        private void printThrowable(Throwable ex, String tag) {\n            StringWriter sw = new StringWriter();\n            PrintWriter pw = new PrintWriter(sw);\n            ex.printStackTrace(pw);\n            pw.flush();\n            String s = sw.toString();\n            int position = 0;\n            int len = s.length();\n            while (position < len) {\n                int index = s.indexOf('\\n', position);\n                if (index == -1) index = len;\n                log(Log.ERROR, tag, s.substring(position, index));\n                position = index + 1;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/RxHttpPlugins.java",
    "content": "package rxhttp;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport okhttp3.Call;\nimport okhttp3.Dispatcher;\nimport okhttp3.OkHttpClient;\nimport rxhttp.wrapper.cache.CacheManager;\nimport rxhttp.wrapper.cache.CacheMode;\nimport rxhttp.wrapper.cache.CacheStrategy;\nimport rxhttp.wrapper.cache.InternalCache;\nimport rxhttp.wrapper.callback.Consumer;\nimport rxhttp.wrapper.callback.IConverter;\nimport rxhttp.wrapper.callback.Function;\nimport rxhttp.wrapper.converter.GsonConverter;\nimport rxhttp.wrapper.param.Param;\nimport rxhttp.wrapper.utils.LogUtil;\n\n/**\n * User: ljx\n * Date: 2019-07-14\n * Time: 11:24\n */\npublic class RxHttpPlugins {\n\n    private static final RxHttpPlugins plugins = new RxHttpPlugins();\n\n    private OkHttpClient okClient;\n\n    private Consumer<? super Param<?>> onParamAssembly;\n    private Function<String, String> decoder;\n    private IConverter converter = GsonConverter.create();\n\n    private List<String> excludeCacheKeys = Collections.emptyList();\n\n    private InternalCache cache;\n    private CacheStrategy cacheStrategy = new CacheStrategy(CacheMode.ONLY_NETWORK);\n\n    private RxHttpPlugins() {\n    }\n\n    public static RxHttpPlugins init(OkHttpClient okHttpClient) {\n        plugins.okClient = okHttpClient;\n        return plugins;\n    }\n\n    public static boolean isInit() {\n        return plugins.okClient != null;\n    }\n\n    public static OkHttpClient getOkHttpClient() {\n        if (plugins.okClient == null)\n            init(getDefaultOkHttpClient());\n        return plugins.okClient;\n    }\n\n    public static OkHttpClient.Builder newOkClientBuilder() {\n        return getOkHttpClient().newBuilder();\n    }\n\n    public RxHttpPlugins setDebug(boolean debug) {\n        return setDebug(debug, false, -1);\n    }\n\n    public RxHttpPlugins setDebug(boolean debug, boolean segmentPrint) {\n        return setDebug(debug, segmentPrint, -1);\n    }\n\n    public RxHttpPlugins setDebug(boolean debug, boolean segmentPrint, int indentSpaces) {\n        LogUtil.setDebug(debug, segmentPrint, indentSpaces);\n        return this;\n    }\n\n    /**\n     * 设置统一公共参数回调接口,通过该接口,可添加公共参数/请求头，每次请求前会回调该接口\n     * 若部分接口不需要添加公共参数,发请求前，调用 RxHttp#setAssemblyEnabled(boolean) 方法设置false即可\n     */\n    public RxHttpPlugins setOnParamAssembly(Consumer<? super Param<?>> onParamAssembly) {\n        this.onParamAssembly = onParamAssembly;\n        return this;\n    }\n\n    /**\n     * 设置统一数据解码/解密器，每次请求成功后会回调该接口并传入Http请求的结果\n     * 通过该接口，可以统一对数据解密，并将解密后的数据返回即可\n     * 若部分接口不需要回调该接口，发请求前，调用 RxHttp#setDecoderEnabled(boolean) 方法设置false即可\n     */\n    public RxHttpPlugins setResultDecoder(Function<String, String> decoder) {\n        this.decoder = decoder;\n        return this;\n    }\n\n    public RxHttpPlugins setConverter(IConverter converter) {\n        if (converter == null)\n            throw new IllegalArgumentException(\"converter can not be null\");\n        this.converter = converter;\n        return this;\n    }\n\n    public static IConverter getConverter() {\n        return plugins.converter;\n    }\n\n    public static void onParamAssembly(@NotNull Param<?> source) {\n        if (!source.isAssemblyEnabled()) return;\n        Consumer<? super Param<?>> consumer = plugins.onParamAssembly;\n        if (consumer != null) {\n            consumer.accept(source);\n        }\n    }\n\n    // Decoder source\n    public static String onResultDecoder(String source) throws IOException {\n        Function<String, String> f = plugins.decoder;\n        return f != null ? f.apply(source) : source;\n    }\n\n    public RxHttpPlugins setCache(File directory, long maxSize) {\n        return setCache(directory, maxSize, CacheMode.ONLY_NETWORK, Long.MAX_VALUE);\n    }\n\n    public RxHttpPlugins setCache(File directory, long maxSize, long cacheValidTime) {\n        return setCache(directory, maxSize, CacheMode.ONLY_NETWORK, cacheValidTime);\n    }\n\n    public RxHttpPlugins setCache(File directory, long maxSize, CacheMode cacheMode) {\n        return setCache(directory, maxSize, cacheMode, Long.MAX_VALUE);\n    }\n\n    public RxHttpPlugins setCache(File directory, long maxSize, CacheMode cacheMode, long cacheValidTime) {\n        if (maxSize <= 0) {\n            throw new IllegalArgumentException(\"maxSize > 0 required but it was \" + maxSize);\n        }\n        CacheManager rxHttpCache = new CacheManager(directory, maxSize);\n        cache = rxHttpCache.internalCache;\n        cacheStrategy = new CacheStrategy(cacheMode, cacheValidTime);\n        return this;\n    }\n\n    public static CacheStrategy getCacheStrategy() {\n        return new CacheStrategy(plugins.cacheStrategy);\n    }\n\n    public static InternalCache getCache() {\n        return plugins.cache;\n    }\n\n    /**\n     * Call {@link RxHttpPlugins#setCache(File,long)} setCache method to set the cache directory and size before using the cache\n     */\n    public static InternalCache getCacheOrThrow() {\n        final InternalCache cache = plugins.cache;\n        if (cache == null) {\n            throw new IllegalArgumentException(\"Call 'setCache(File,long)' method to set the cache directory and size before using the cache\");\n        }\n        return cache;\n    }\n\n    public RxHttpPlugins setExcludeCacheKeys(String... keys) {\n        excludeCacheKeys = Arrays.asList(keys);\n        return this;\n    }\n\n    public static List<String> getExcludeCacheKeys() {\n        return plugins.excludeCacheKeys;\n    }\n\n    //Cancel all requests\n    public static void cancelAll() {\n        cancelAll(plugins.okClient);\n    }\n\n    //Cancel the request according to tag\n    public static void cancelAll(Object tag) {\n        cancelAll(plugins.okClient, tag);\n    }\n\n    public static void cancelAll(@Nullable OkHttpClient okClient) {\n        if (okClient == null) return;\n        okClient.dispatcher().cancelAll();\n    }\n\n    public static void cancelAll(@Nullable OkHttpClient okClient, @Nullable Object tag) {\n        if (tag == null || okClient == null) return;\n        Dispatcher dispatcher = okClient.dispatcher();\n\n        for (Call call : dispatcher.queuedCalls()) {\n            if (tag.equals(call.request().tag())) {\n                call.cancel();\n            }\n        }\n\n        for (Call call : dispatcher.runningCalls()) {\n            if (tag.equals(call.request().tag())) {\n                call.cancel();\n            }\n        }\n    }\n\n    //Default OkHttpClient object in RxHttp\n    private static OkHttpClient getDefaultOkHttpClient() {\n        return new OkHttpClient.Builder().build();\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/CallFactory.kt",
    "content": "package rxhttp.wrapper\n\nimport okhttp3.Call\nimport rxhttp.wrapper.param.AbstractBodyParam\n\n/**\n * User: ljx\n * Date: 2020/3/21\n * Time: 23:56\n */\ninterface CallFactory {\n\n    fun newCall(): Call\n}\n\ninterface ITag {\n\n    fun <T> tag(type: Class<in T>, tag: T): CallFactory\n}\n\ninterface BodyParamFactory : CallFactory {\n    val param: AbstractBodyParam<*>\n}\n\n\n\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/OkHttpCompat.java",
    "content": "package rxhttp.wrapper;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.Closeable;\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.util.List;\n\nimport okhttp3.CookieJar;\nimport okhttp3.Headers;\nimport okhttp3.HttpUrl;\nimport okhttp3.MediaType;\nimport okhttp3.MultipartBody;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\nimport okhttp3.internal.cache.DiskLruCache;\nimport okhttp3.internal.cache.DiskLruCache.Companion;\nimport okhttp3.internal.concurrent.TaskRunner;\nimport okhttp3.internal.http.StatusLine;\nimport okio.Buffer;\nimport okio.ByteString;\nimport okio.FileSystem;\nimport okio.Path;\nimport rxhttp.wrapper.exception.HttpStatusCodeException;\nimport rxhttp.wrapper.param.Param;\nimport rxhttp.wrapper.utils.Utils;\n\n/**\n * 此类的作用在于兼用OkHttp版本  注意: 本类一定要用Java语言编写，kotlin将无法兼容新老版本\n * User: ljx\n * Date: 2020/5/17\n * Time: 15:28\n */\npublic class OkHttpCompat {\n\n    private static String OKHTTP_USER_AGENT;\n\n    public static boolean needDecodeResult(Response response) {\n        return !\"false\".equals(response.request().header(Param.DATA_DECRYPT));\n    }\n\n    public static void closeQuietly(Closeable... closeables) {\n        if (closeables == null) return;\n        for (Closeable closeable : closeables) {\n            if (closeable == null) continue;\n            Utils.closeQuietly(closeable);\n        }\n    }\n\n    public static ResponseBody buffer(final ResponseBody body) throws IOException {\n        Buffer buffer = new Buffer();\n        body.source().readAll(buffer);\n        return ResponseBody.create(body.contentType(), body.contentLength(), buffer);\n    }\n\n    public static RequestBody create(@Nullable MediaType contentType, String content) {\n        return RequestBody.create(contentType, content);\n    }\n\n    public static RequestBody create(final @Nullable MediaType contentType, final ByteString content) {\n        return RequestBody.create(contentType, content);\n    }\n\n    public static RequestBody create(final @Nullable MediaType contentType, final byte[] content,\n                                     final int offset, final int byteCount) {\n        return RequestBody.create(contentType, content, offset, byteCount);\n    }\n\n    public static MultipartBody.Part part(RequestBody body) {\n        return MultipartBody.Part.create(body);\n    }\n\n    public static MultipartBody.Part part(@Nullable Headers headers, RequestBody body) {\n        return MultipartBody.Part.create(headers, body);\n    }\n\n    public static MultipartBody.Part part(String name, @Nullable String filename, RequestBody body) {\n        return MultipartBody.Part.createFormData(name, filename, body);\n    }\n\n    public static Request request(Response response) {\n        return response.request();\n    }\n\n    public static List<String> pathSegments(Response response) {\n        return response.request().url().pathSegments();\n    }\n\n    public static HttpUrl url(Request request) {\n        return request.url();\n    }\n\n    public static CookieJar cookieJar(OkHttpClient okHttpClient) {\n        return okHttpClient.cookieJar();\n    }\n\n    public static String header(Response response, String name) {\n        return response.header(name);\n    }\n\n    public static boolean isPartialContent(Response response) {\n        return response.code() == 206;\n    }\n\n    public static long receivedResponseAtMillis(Response response) {\n        return response.receivedResponseAtMillis();\n    }\n\n    @NotNull\n    public static ResponseBody throwIfFail(Response response) throws IOException {\n        ResponseBody rawBody = response.body();\n        if (!response.isSuccessful()) {\n            try {\n                ResponseBody bufferBody = buffer(rawBody);\n                response = response.newBuilder().body(bufferBody).build();\n                throw new HttpStatusCodeException(response);\n            } finally {\n                rawBody.close();\n            }\n        }\n        return rawBody;\n    }\n\n    //从响应头 Content-Range 中，取 contentLength\n    public static long getContentLength(Response response) {\n        long contentLength = -1;\n        ResponseBody body = response.body();\n        if (body != null) {\n            if ((contentLength = body.contentLength()) != -1) {\n                return contentLength;\n            }\n        }\n        String headerValue = response.header(\"Content-Range\");\n        if (headerValue != null) {\n            //响应头Content-Range格式 : bytes 100001-20000000/20000001\n            try {\n                int divideIndex = headerValue.indexOf(\"/\"); //斜杠下标\n                int blankIndex = headerValue.indexOf(\" \");\n                String fromToValue = headerValue.substring(blankIndex + 1, divideIndex);\n                String[] split = fromToValue.split(\"-\");\n                long start = Long.parseLong(split[0]); //开始下载位置\n                long end = Long.parseLong(split[1]);   //结束下载位置\n                contentLength = end - start + 1;       //要下载的总长度\n            } catch (Exception ignore) {\n            }\n        }\n        return contentLength;\n    }\n\n    //解析http状态行\n    public static StatusLine parse(String statusLine) throws IOException {\n        if (okHttpVersionCompare(\"4.0.0\") >= 0) {\n            return StatusLine.Companion.parse(statusLine);\n        } else {\n            Class<StatusLine> statusLineClass = StatusLine.class;\n            try {\n                Method parse = statusLineClass.getDeclaredMethod(\"parse\", String.class);\n                return (StatusLine) parse.invoke(statusLineClass, statusLine);\n            } catch (Exception e) {\n                throw new IOException(e.getMessage());\n            }\n        }\n    }\n\n    public static DiskLruCache newDiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {\n        if (okHttpVersionCompare(\"5.0.0\") >= 0) {\n            return new DiskLruCache(FileSystem.SYSTEM, Path.get(directory), appVersion, valueCount, maxSize, TaskRunner.INSTANCE);\n        } else {\n            Class<?> fileSystemClass;\n            Object fileSystem;\n            try {\n                fileSystemClass = Class.forName(\"okhttp3.internal.io.FileSystem\");\n                fileSystem = fileSystemClass.getDeclaredField(\"SYSTEM\").get(null);\n            } catch (Throwable e) {\n                throw new RuntimeException(e);\n            }\n            if (okHttpVersionCompare(\"4.3.0\") >= 0) {\n                try {\n                    Constructor<DiskLruCache> constructor = DiskLruCache.class.getConstructor(fileSystemClass, File.class, int.class, int.class, long.class, TaskRunner.class);\n                    return constructor.newInstance(fileSystem, directory, appVersion, valueCount, maxSize, TaskRunner.INSTANCE);\n                } catch (Throwable ignore) {\n                }\n            } else if (okHttpVersionCompare(\"4.0.0\") >= 0) {\n                Companion companion = DiskLruCache.Companion;\n                Class<? extends Companion> clazz = companion.getClass();\n                try {\n                    Method create = clazz.getDeclaredMethod(\"create\", fileSystemClass, File.class, int.class, int.class, long.class);\n                    return (DiskLruCache) create.invoke(companion, fileSystem, directory, appVersion, valueCount, maxSize);\n                } catch (Throwable ignore) {\n                }\n            } else {\n                Class<DiskLruCache> clazz = DiskLruCache.class;\n                try {\n                    Method create = clazz.getDeclaredMethod(\"create\", fileSystemClass, File.class, int.class, int.class, long.class);\n                    return (DiskLruCache) create.invoke(null, fileSystem, directory, appVersion, valueCount, maxSize);\n                } catch (Throwable ignore) {\n                }\n            }\n        }\n        throw new RuntimeException(\"Please upgrade OkHttp to V3.12.0 or higher\");\n    }\n\n    //okhttp版本比较，当前版本大于version2，返回 >0; 等于，返回=0; 否则，返回 <0\n    public static int okHttpVersionCompare(String version2) {\n        String[] okHttpUserAgentArr = getOkHttpUserAgent().split(\"/\");\n        String okhttpVersion = okHttpUserAgentArr[okHttpUserAgentArr.length - 1];\n        return versionCompare(okhttpVersion, version2);\n    }\n\n    //获取OkHttp版本号\n    public static String getOkHttpUserAgent() {\n        if (OKHTTP_USER_AGENT != null) return OKHTTP_USER_AGENT;\n        try {\n            //5.0.0及以上版本获取userAgent方式\n            Class<?> clazz = Class.forName(\"okhttp3.internal._UtilCommonKt\");\n            return OKHTTP_USER_AGENT = (String) clazz.getDeclaredField(\"USER_AGENT\").get(null);\n        } catch (Throwable ignore) {\n        }\n\n        try {\n            //4.7.x及以上版本获取userAgent方式\n            Class<?> clazz = Class.forName(\"okhttp3.internal.Util\");\n            return OKHTTP_USER_AGENT = (String) clazz.getDeclaredField(\"userAgent\").get(null);\n        } catch (Throwable ignore) {\n        }\n        try {\n            Class<?> clazz = Class.forName(\"okhttp3.internal.Version\");\n            try {\n                //4.x.x及以上版本获取userAgent方式\n                Field userAgent = clazz.getDeclaredField(\"userAgent\");\n                return OKHTTP_USER_AGENT = (String) userAgent.get(null);\n            } catch (Exception ignore) {\n            }\n            //4.x.x以下版本获取userAgent方式\n            Method userAgent = clazz.getDeclaredMethod(\"userAgent\");\n            return OKHTTP_USER_AGENT = (String) userAgent.invoke(null);\n        } catch (Throwable ignore) {\n        }\n        return OKHTTP_USER_AGENT = \"okhttp/x.x.x\";\n    }\n\n    private static int versionCompare(String version1, String version2) {\n        String[] versionArr1 = version1.split(\"\\\\.\");\n        String[] versionArr2 = version2.split(\"\\\\.\");\n        int minLen = Math.min(versionArr1.length, versionArr2.length);\n        int diff = 0;\n        for (int i = 0; i < minLen; i++) {\n            String v1 = versionArr1[i];\n            String v2 = versionArr2[i];\n            diff = v1.length() - v2.length();\n            if (diff == 0) {\n                diff = v1.compareTo(v2);\n            }\n            if (diff != 0) {\n                break;\n            }\n        }\n        diff = (diff != 0) ? diff : (versionArr1.length - versionArr2.length);\n        return diff;\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/cache/CacheManager.java",
    "content": "package rxhttp.wrapper.cache;\n\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.Flushable;\nimport java.io.IOException;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateEncodingException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CertificateFactory;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.NoSuchElementException;\n\nimport okhttp3.CipherSuite;\nimport okhttp3.Handshake;\nimport okhttp3.Headers;\nimport okhttp3.MediaType;\nimport okhttp3.Protocol;\nimport okhttp3.Request;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\nimport okhttp3.TlsVersion;\nimport okhttp3.internal.cache.CacheRequest;\nimport okhttp3.internal.cache.DiskLruCache;\nimport okhttp3.internal.http.RealResponseBody;\nimport okhttp3.internal.http.StatusLine;\nimport okhttp3.internal.platform.Platform;\nimport okio.Buffer;\nimport okio.BufferedSink;\nimport okio.BufferedSource;\nimport okio.ByteString;\nimport okio.ForwardingSink;\nimport okio.ForwardingSource;\nimport okio.Okio;\nimport okio.Sink;\nimport okio.Source;\nimport okio.Timeout;\nimport rxhttp.wrapper.OkHttpCompat;\nimport rxhttp.wrapper.utils.Utils;\n\n/**\n * User: ljx\n * Date: 2019-12-14\n * Time: 16:59\n */\npublic class CacheManager implements Closeable, Flushable {\n\n    private static final int VERSION = 201105;\n    private static final int ENTRY_METADATA = 0;\n    private static final int ENTRY_BODY = 1;\n    private static final int ENTRY_COUNT = 2;\n\n    public final InternalCache internalCache = new InternalCache() {\n        @Nullable\n        @Override\n        public Response get(Request request, String key) throws IOException {\n            return CacheManager.this.get(request, key);\n        }\n\n        @Override\n        public Response put(Response response, String key) throws IOException {\n            return CacheManager.this.put(response, key);\n        }\n\n        @Override\n        public void remove(String key) throws IOException {\n            CacheManager.this.remove(key);\n        }\n\n        @Override\n        public void removeAll() throws IOException {\n            CacheManager.this.evictAll();\n        }\n\n        @Override\n        public long size() throws IOException {\n            return CacheManager.this.size();\n        }\n    };\n\n    private final DiskLruCache cache;\n\n    /**\n     * Create a cache of at most {@code maxSize} bytes in {@code directory}.\n     *\n     * @param directory File\n     * @param maxSize   long\n     */\n    public CacheManager(File directory, long maxSize) {\n        this.cache = OkHttpCompat.newDiskLruCache(directory, VERSION, ENTRY_COUNT, maxSize);\n    }\n\n\n    public static String md5(String key) {\n        return ByteString.encodeUtf8(key).md5().hex();\n    }\n\n    @Nullable\n    private Response get(Request request, String key) {\n        String md5Key = md5(key != null ? key : request.url().toString());\n        DiskLruCache.Snapshot snapshot;\n        CacheManager.Entry entry;\n        try {\n            snapshot = cache.get(md5Key);\n            if (snapshot == null) {\n                return null;\n            }\n        } catch (IOException e) {\n            // Give up because the cache cannot be read.\n            return null;\n        }\n\n        try {\n            entry = new CacheManager.Entry(snapshot.getSource(ENTRY_METADATA));\n        } catch (IOException e) {\n            Utils.closeQuietly(snapshot);\n            return null;\n        }\n\n        return entry.response(request, snapshot);\n    }\n\n    private Response put(Response networkResponse, String key) throws IOException {\n        CacheRequest cacheRequest = putResponse(networkResponse, key); //写响应头、message等信息\n        return cacheWritingResponse(cacheRequest, networkResponse);   //写\n    }\n\n    @Nullable\n    private CacheRequest putResponse(Response response, String key) {\n        CacheManager.Entry entry = new CacheManager.Entry(response);\n        DiskLruCache.Editor editor = null;\n        try {\n            String md5Key = md5(key != null ? key : response.request().url().toString());\n            editor = cache.edit(md5Key);\n            if (editor == null) {\n                return null;\n            }\n            entry.writeTo(editor);\n            return new CacheManager.CacheRequestImpl(editor);\n        } catch (IOException e) {\n            abortQuietly(editor);\n            return null;\n        }\n    }\n\n    private okhttp3.Response cacheWritingResponse(final CacheRequest cacheRequest, okhttp3.Response response)\n        throws IOException {\n        // Some apps return a null body; for compatibility we treat that like a null cache request.\n        if (cacheRequest == null) return response;\n        Sink cacheBodyUnbuffered = cacheRequest.body();\n        ResponseBody body = response.body();\n        if (body == null) return response;\n\n        final BufferedSource source = body.source();\n        final BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered);\n\n        Source cacheWritingSource = new Source() {\n            boolean cacheRequestClosed;\n\n            @Override\n            public long read(@NotNull Buffer sink, long byteCount) throws IOException {\n                long bytesRead;\n                try {\n                    bytesRead = source.read(sink, byteCount);\n                } catch (IOException e) {\n                    if (!cacheRequestClosed) {\n                        cacheRequestClosed = true;\n                        cacheRequest.abort(); // Failed to write a complete cache response.\n                    }\n                    throw e;\n                }\n\n                if (bytesRead == -1) {\n                    if (!cacheRequestClosed) {\n                        cacheRequestClosed = true;\n                        cacheBody.close(); // The cache response is complete!\n                    }\n                    return -1;\n                }\n\n                sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead);\n                cacheBody.emitCompleteSegments();\n                return bytesRead;\n            }\n\n            @NotNull\n            @Override\n            public Timeout timeout() {\n                return source.timeout();\n            }\n\n            @Override\n            public void close() throws IOException {\n                //这里本应传入ExchangeCodec.DISCARD_STREAM_TIMEOUT_MILLIS常量，但为兼容老版本，故直接传入常量对应的值100\n                if (!cacheRequestClosed\n                    && !Utils.discard(this, 100, MILLISECONDS)) {\n                    cacheRequestClosed = true;\n                    cacheRequest.abort();\n                }\n                source.close();\n            }\n        };\n\n        String contentType = response.header(\"Content-Type\");\n        long contentLength = response.body().contentLength();\n        return response.newBuilder()\n            .body(new RealResponseBody(contentType, contentLength, Okio.buffer(cacheWritingSource)))\n            .build();\n    }\n\n    private void remove(String key) throws IOException {\n        cache.remove(md5(key));\n    }\n\n    private void abortQuietly(@Nullable DiskLruCache.Editor editor) {\n        // Give up because the cache cannot be written.\n        try {\n            if (editor != null) {\n                editor.abort();\n            }\n        } catch (IOException ignored) {\n        }\n    }\n\n    /**\n     * Initialize the cache. This will include reading the journal files from the storage and building\n     * up the necessary in-memory cache information.\n     *\n     * <p>The initialization time may vary depending on the journal file size and the current actual\n     * cache size. The application needs to be aware of calling this function during the\n     * initialization phase and preferably in a background worker thread.\n     *\n     * <p>Note that if the application chooses to not call this method to initialize the cache. By\n     * default, the okhttp will perform lazy initialization upon the first usage of the cache.\n     *\n     * @throws IOException 初始化失败\n     */\n    public void initialize() throws IOException {\n        cache.initialize();\n    }\n\n    /**\n     * Closes the cache and deletes all of its stored values. This will delete all files in the cache\n     * directory including files that weren't created by the cache.\n     */\n    private void delete() throws IOException {\n        cache.delete();\n    }\n\n    /**\n     * Deletes all values stored in the cache. In-flight writes to the cache will complete normally,\n     * but the corresponding responses will not be stored.\n     */\n    private void evictAll() throws IOException {\n        cache.evictAll();\n    }\n\n\n    public Iterator<String> urls() throws IOException {\n        return new Iterator<String>() {\n            final Iterator<DiskLruCache.Snapshot> delegate = cache.snapshots();\n\n            @Nullable String nextUrl;\n            boolean canRemove;\n\n            @Override\n            public boolean hasNext() {\n                if (nextUrl != null) return true;\n\n                canRemove = false; // Prevent delegate.remove() on the wrong item!\n                while (delegate.hasNext()) {\n                    try (DiskLruCache.Snapshot snapshot = delegate.next()) {\n                        BufferedSource metadata = Okio.buffer(snapshot.getSource(ENTRY_METADATA));\n                        nextUrl = metadata.readUtf8LineStrict();\n                        return true;\n                    } catch (IOException ignored) {\n                        // We couldn't read the metadata for this snapshot; possibly because the host filesystem\n                        // has disappeared! Skip it.\n                    }\n                }\n\n                return false;\n            }\n\n            @Override\n            public String next() {\n                if (!hasNext()) throw new NoSuchElementException();\n                String result = nextUrl;\n                nextUrl = null;\n                canRemove = true;\n                return result;\n            }\n\n            @Override\n            public void remove() {\n                if (!canRemove) throw new IllegalStateException(\"remove() before next()\");\n                delegate.remove();\n            }\n        };\n    }\n\n\n    public long size() throws IOException {\n        return cache.size();\n    }\n\n    /**\n     * @return Max size of the cache (in bytes).\n     */\n    public long maxSize() {\n        return cache.getMaxSize();\n    }\n\n    @Override\n    public void flush() throws IOException {\n        cache.flush();\n    }\n\n    @Override\n    public void close() throws IOException {\n        cache.close();\n    }\n\n    public File directory() {\n        return cache.getDirectory().toFile();\n    }\n\n    public boolean isClosed() {\n        return cache.isClosed();\n    }\n\n    private final class CacheRequestImpl implements CacheRequest {\n        private final DiskLruCache.Editor editor;\n        private final Sink cacheOut;\n        private final Sink body;\n        boolean done;\n\n        CacheRequestImpl(final DiskLruCache.Editor editor) {\n            this.editor = editor;\n            this.cacheOut = editor.newSink(ENTRY_BODY);\n            this.body = new ForwardingSink(cacheOut) {\n                @Override\n                public void close() throws IOException {\n                    synchronized (CacheManager.this) {\n                        if (done) {\n                            return;\n                        }\n                        done = true;\n                    }\n                    super.close();\n                    editor.commit();\n                }\n            };\n        }\n\n        @Override\n        public void abort() {\n            synchronized (CacheManager.this) {\n                if (done) {\n                    return;\n                }\n                done = true;\n            }\n            Utils.closeQuietly(cacheOut);\n            try {\n                editor.abort();\n            } catch (IOException ignored) {\n            }\n        }\n\n        @NotNull\n        @Override\n        public Sink body() {\n            return body;\n        }\n    }\n\n    private static final class Entry {\n        /**\n         * Synthetic response header: the local time when the request was sent.\n         */\n        private static final String SENT_MILLIS = Platform.get().getPrefix() + \"-Sent-Millis\";\n\n        /**\n         * Synthetic response header: the local time when the response was received.\n         */\n        private static final String RECEIVED_MILLIS = Platform.get().getPrefix() + \"-Received-Millis\";\n\n        private final String url;\n        private final Headers varyHeaders;\n        private final String requestMethod;\n        private final Protocol protocol;\n        private final int code;\n        private final String message;\n        private final Headers responseHeaders;\n        private final @Nullable Handshake handshake;\n        private final long sentRequestMillis;\n        private final long receivedResponseMillis;\n\n        /**\n         * Reads an entry from an input stream. A typical entry looks like this:\n         * <pre>{@code\n         *   http://google.com/foo\n         *   GET\n         *   2\n         *   Accept-Language: fr-CA\n         *   Accept-Charset: UTF-8\n         *   HTTP/1.1 200 OK\n         *   3\n         *   Content-Type: image/png\n         *   Content-Length: 100\n         *   RxHttpCache-Control: max-age=600\n         * }</pre>\n         *\n         * <p>A typical HTTPS file looks like this:\n         * <pre>{@code\n         *   https://google.com/foo\n         *   GET\n         *   2\n         *   Accept-Language: fr-CA\n         *   Accept-Charset: UTF-8\n         *   HTTP/1.1 200 OK\n         *   3\n         *   Content-Type: image/png\n         *   Content-Length: 100\n         *   Cache-Control: max-age=600\n         *\n         *   AES_256_WITH_MD5\n         *   2\n         *   base64-encoded peerCertificate[0]\n         *   base64-encoded peerCertificate[1]\n         *   -1\n         *   TLSv1.2\n         * }</pre>\n         * The file is newline separated. The first two lines are the URL and the request method. Next\n         * is the number of HTTP Vary request header lines, followed by those lines.\n         *\n         * <p>Next is the response status line, followed by the number of HTTP response header lines,\n         * followed by those lines.\n         *\n         * <p>HTTPS responses also contain SSL session information. This begins with a blank line, and\n         * then a line containing the cipher suite. Next is the length of the peer certificate chain.\n         * These certificates are base64-encoded and appear each on their own line. The next line\n         * contains the length of the local certificate chain. These certificates are also\n         * base64-encoded and appear each on their own line. A length of -1 is used to encode a null\n         * array. The last line is optional. If present, it contains the TLS version.\n         */\n        Entry(Source in) throws IOException {\n            try {\n                BufferedSource source = Okio.buffer(in);\n                url = source.readUtf8LineStrict();\n                requestMethod = source.readUtf8LineStrict();\n                Headers.Builder varyHeadersBuilder = new Headers.Builder();\n                int varyRequestHeaderLineCount = readInt(source);\n                for (int i = 0; i < varyRequestHeaderLineCount; i++) {\n                    addUnsafeNonAscii(varyHeadersBuilder, source.readUtf8LineStrict());\n                }\n                varyHeaders = varyHeadersBuilder.build();\n\n                StatusLine statusLine = OkHttpCompat.parse(source.readUtf8LineStrict());\n                protocol = statusLine.protocol;\n                code = statusLine.code;\n                message = statusLine.message;\n                Headers.Builder responseHeadersBuilder = new Headers.Builder();\n                int responseHeaderLineCount = readInt(source);\n                for (int i = 0; i < responseHeaderLineCount; i++) {\n                    addUnsafeNonAscii(responseHeadersBuilder, source.readUtf8LineStrict());\n                }\n                String sendRequestMillisString = responseHeadersBuilder.get(SENT_MILLIS);\n                String receivedResponseMillisString = responseHeadersBuilder.get(RECEIVED_MILLIS);\n                responseHeadersBuilder.removeAll(SENT_MILLIS);\n                responseHeadersBuilder.removeAll(RECEIVED_MILLIS);\n                sentRequestMillis = sendRequestMillisString != null\n                    ? Long.parseLong(sendRequestMillisString)\n                    : 0L;\n                receivedResponseMillis = receivedResponseMillisString != null\n                    ? Long.parseLong(receivedResponseMillisString)\n                    : 0L;\n                responseHeaders = responseHeadersBuilder.build();\n\n                if (isHttps()) {\n                    String blank = source.readUtf8LineStrict();\n                    if (blank.length() > 0) {\n                        throw new IOException(\"expected \\\"\\\" but was \\\"\" + blank + \"\\\"\");\n                    }\n                    String cipherSuiteString = source.readUtf8LineStrict();\n                    CipherSuite cipherSuite = CipherSuite.forJavaName(cipherSuiteString);\n                    List<Certificate> peerCertificates = readCertificateList(source);\n                    List<Certificate> localCertificates = readCertificateList(source);\n                    TlsVersion tlsVersion = !source.exhausted()\n                        ? TlsVersion.forJavaName(source.readUtf8LineStrict())\n                        : TlsVersion.SSL_3_0;\n                    handshake = Handshake.get(tlsVersion, cipherSuite, peerCertificates, localCertificates);\n                } else {\n                    handshake = null;\n                }\n            } finally {\n                in.close();\n            }\n        }\n\n        /**\n         * Add a header with the specified name and value. Does validation of header names, allowing\n         * non-ASCII values.\n         */\n        void addUnsafeNonAscii(Headers.Builder builder, String line) {\n            int index = line.indexOf(\":\", 1);\n            if (index != -1) {\n                builder.addUnsafeNonAscii(line.substring(0, index), line.substring(index + 1));\n            } else if (line.startsWith(\":\")) {\n                builder.addUnsafeNonAscii(\"\", line.substring(1));\n            } else {\n                builder.addUnsafeNonAscii(\"\", line);\n            }\n        }\n\n        Entry(Response response) {\n            this.url = response.request().url().toString();\n            this.varyHeaders = HeadersVary.varyHeaders(response);\n            this.requestMethod = response.request().method();\n            this.protocol = response.protocol();\n            this.code = response.code();\n            this.message = response.message();\n            this.responseHeaders = response.headers();\n            this.handshake = response.handshake();\n            this.sentRequestMillis = response.sentRequestAtMillis();\n            this.receivedResponseMillis = response.receivedResponseAtMillis();\n        }\n\n        public void writeTo(DiskLruCache.Editor editor) throws IOException {\n            BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));\n\n            sink.writeUtf8(url)\n                .writeByte('\\n');\n            sink.writeUtf8(requestMethod)\n                .writeByte('\\n');\n            sink.writeDecimalLong(varyHeaders.size())\n                .writeByte('\\n');\n            for (int i = 0, size = varyHeaders.size(); i < size; i++) {\n                sink.writeUtf8(varyHeaders.name(i))\n                    .writeUtf8(\": \")\n                    .writeUtf8(varyHeaders.value(i))\n                    .writeByte('\\n');\n            }\n            sink.writeUtf8(new StatusLine(protocol, code, message).toString())\n                .writeByte('\\n');\n            sink.writeDecimalLong(responseHeaders.size() + 2)\n                .writeByte('\\n');\n            for (int i = 0, size = responseHeaders.size(); i < size; i++) {\n                sink.writeUtf8(responseHeaders.name(i))\n                    .writeUtf8(\": \")\n                    .writeUtf8(responseHeaders.value(i))\n                    .writeByte('\\n');\n            }\n            sink.writeUtf8(SENT_MILLIS)\n                .writeUtf8(\": \")\n                .writeDecimalLong(sentRequestMillis)\n                .writeByte('\\n');\n            sink.writeUtf8(RECEIVED_MILLIS)\n                .writeUtf8(\": \")\n                .writeDecimalLong(receivedResponseMillis)\n                .writeByte('\\n');\n\n            if (isHttps()) {\n                sink.writeByte('\\n');\n                sink.writeUtf8(handshake.cipherSuite().javaName())\n                    .writeByte('\\n');\n                writeCertList(sink, handshake.peerCertificates());\n                writeCertList(sink, handshake.localCertificates());\n                sink.writeUtf8(handshake.tlsVersion().javaName()).writeByte('\\n');\n            }\n            sink.close();\n        }\n\n        private boolean isHttps() {\n            return url.startsWith(\"https://\");\n        }\n\n        private List<Certificate> readCertificateList(BufferedSource source) throws IOException {\n            int length = readInt(source);\n            if (length == -1)\n                return Collections.emptyList(); // OkHttp v1.2 used -1 to indicate null.\n\n            try {\n                CertificateFactory certificateFactory = CertificateFactory.getInstance(\"X.509\");\n                List<Certificate> result = new ArrayList<>(length);\n                for (int i = 0; i < length; i++) {\n                    String line = source.readUtf8LineStrict();\n                    Buffer bytes = new Buffer();\n                    bytes.write(ByteString.decodeBase64(line));\n                    result.add(certificateFactory.generateCertificate(bytes.inputStream()));\n                }\n                return result;\n            } catch (CertificateException e) {\n                throw new IOException(e.getMessage());\n            }\n        }\n\n        private void writeCertList(BufferedSink sink, List<Certificate> certificates)\n            throws IOException {\n            try {\n                sink.writeDecimalLong(certificates.size())\n                    .writeByte('\\n');\n                for (int i = 0, size = certificates.size(); i < size; i++) {\n                    byte[] bytes = certificates.get(i).getEncoded();\n                    String line = ByteString.of(bytes).base64();\n                    sink.writeUtf8(line)\n                        .writeByte('\\n');\n                }\n            } catch (CertificateEncodingException e) {\n                throw new IOException(e.getMessage());\n            }\n        }\n\n        public boolean matches(Request request, Response response) {\n            return url.equals(request.url().toString())\n                && requestMethod.equals(request.method())\n                && HeadersVary.varyMatches(response, varyHeaders, request);\n        }\n\n        public Response response(Request request, DiskLruCache.Snapshot snapshot) {\n            String contentType = responseHeaders.get(\"Content-Type\");\n            String contentLength = responseHeaders.get(\"Content-Length\");\n\n            return new Response.Builder()\n                .request(request)\n                .protocol(protocol)\n                .code(code)\n                .message(message)\n                .headers(responseHeaders)\n                .body(new CacheManager.CacheResponseBody(snapshot, contentType, contentLength))\n                .handshake(handshake)\n                .sentRequestAtMillis(sentRequestMillis)\n                .receivedResponseAtMillis(receivedResponseMillis)\n                .build();\n        }\n    }\n\n    private static int readInt(BufferedSource source) throws IOException {\n        try {\n            long result = source.readDecimalLong();\n            String line = source.readUtf8LineStrict();\n            if (result < 0 || result > Integer.MAX_VALUE || !line.isEmpty()) {\n                throw new IOException(\"expected an int but was \\\"\" + result + line + \"\\\"\");\n            }\n            return (int) result;\n        } catch (NumberFormatException e) {\n            throw new IOException(e.getMessage());\n        }\n    }\n\n    private static class CacheResponseBody extends ResponseBody {\n        final DiskLruCache.Snapshot snapshot;\n        private final BufferedSource bodySource;\n        private final @Nullable String contentType;\n        private final @Nullable String contentLength;\n\n        CacheResponseBody(final DiskLruCache.Snapshot snapshot,\n                          String contentType, String contentLength) {\n            this.snapshot = snapshot;\n            this.contentType = contentType;\n            this.contentLength = contentLength;\n\n            Source source = snapshot.getSource(ENTRY_BODY);\n            bodySource = Okio.buffer(new ForwardingSource(source) {\n                @Override\n                public void close() throws IOException {\n                    snapshot.close();\n                    super.close();\n                }\n            });\n        }\n\n        @Override\n        public MediaType contentType() {\n            return contentType != null ? MediaType.parse(contentType) : null;\n        }\n\n        @Override\n        public long contentLength() {\n            try {\n                return contentLength != null ? Long.parseLong(contentLength) : -1;\n            } catch (NumberFormatException e) {\n                return -1;\n            }\n        }\n\n        @NotNull\n        @Override\n        public BufferedSource source() {\n            return bodySource;\n        }\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/cache/CacheMode.java",
    "content": "package rxhttp.wrapper.cache;\n\n/**\n * User: ljx\n * Date: 2019-12-15\n * Time: 13:57\n */\npublic enum CacheMode {\n\n    /**\n     * 仅请求网络，默认模式  （不写缓存）\n     */\n    ONLY_NETWORK,\n\n    /**\n     * 仅读取缓存  （不写缓存）\n     */\n    ONLY_CACHE,\n\n    /**\n     * 请求成功后，写入缓存 跟{@link #ONLY_NETWORK } 默认模式相比，仅多了写缓存的操作\n     */\n    NETWORK_SUCCESS_WRITE_CACHE,\n\n    /**\n     * 先读取缓存，失败后再请求网络  (网络请求成功，写缓存)\n     */\n    READ_CACHE_FAILED_REQUEST_NETWORK,\n\n    /**\n     * 先请求网络，失败后再读取缓存  (网络请求成功，写缓存)\n     */\n    REQUEST_NETWORK_FAILED_READ_CACHE,\n    ;\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/cache/CacheStrategy.java",
    "content": "package rxhttp.wrapper.cache;\n\n/**\n * 缓存策略\n * User: ljx\n * Date: 2019-12-15\n * Time: 20:58\n */\npublic class CacheStrategy {\n\n    private String cacheKey; //缓存读写时的key\n    private long cacheValidTime = Long.MAX_VALUE; //缓存有效时间  默认Long.MAX_VALUE，代表永久有效\n    private CacheMode cacheMode; //缓存模式\n\n    public CacheStrategy(CacheStrategy cacheStrategy) {\n        this.cacheKey = cacheStrategy.cacheKey;\n        this.cacheMode = cacheStrategy.cacheMode;\n        setCacheValidTime(cacheStrategy.cacheValidTime);\n    }\n\n    public CacheStrategy(CacheMode cacheMode) {\n        this.cacheMode = cacheMode;\n    }\n\n    public CacheStrategy(CacheMode cacheMode, long cacheValidTime) {\n        this.cacheMode = cacheMode;\n        setCacheValidTime(cacheValidTime);\n    }\n\n    public String getCacheKey() {\n        return cacheKey;\n    }\n\n    public void setCacheKey(String key) {\n        this.cacheKey = key;\n    }\n\n    public long getCacheValidTime() {\n        return cacheValidTime;\n    }\n\n    public void setCacheValidTime(long validTime) {\n        if (validTime <= 0) {\n            throw new IllegalArgumentException(\"validTime > 0 required but it was \" + validTime);\n        }\n        this.cacheValidTime = validTime;\n    }\n\n    public CacheMode getCacheMode() {\n        return cacheMode;\n    }\n\n    public void setCacheMode(CacheMode cacheMode) {\n        this.cacheMode = cacheMode;\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/cache/HeadersVary.java",
    "content": "package rxhttp.wrapper.cache;\n\nimport java.util.Collections;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.TreeSet;\n\nimport okhttp3.Headers;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\n\n/**\n * User: ljx\n * Date: 2020/5/15\n * Time: 23:14\n */\nclass HeadersVary {\n\n    /**\n     * Returns true if none of the Vary headers have changed between {@code cachedRequest} and {@code\n     * newRequest}.\n     */\n    static boolean varyMatches(\n        Response cachedResponse, Headers cachedRequest, Request newRequest) {\n        for (String field : varyFields(cachedResponse)) {\n            if (!equal(cachedRequest.values(field), newRequest.headers(field))) return false;\n        }\n        return true;\n    }\n\n    /**\n     * Returns the subset of the headers in {@code response}'s request that impact the content of\n     * response's body.\n     */\n    static Headers varyHeaders(Response response) {\n        // Use the request headers sent over the network, since that's what the\n        // response varies on. Otherwise OkHttp-supplied headers like\n        // \"Accept-Encoding: gzip\" may be lost.\n        Headers requestHeaders = response.networkResponse().request().headers();\n        Headers responseHeaders = response.headers();\n        return varyHeaders(requestHeaders, responseHeaders);\n    }\n\n    /**\n     * Returns the subset of the headers in {@code requestHeaders} that impact the content of\n     * response's body.\n     */\n    private static Headers varyHeaders(Headers requestHeaders, Headers responseHeaders) {\n        Set<String> varyFields = varyFields(responseHeaders);\n        if (varyFields.isEmpty()) return new Headers.Builder().build();\n\n        Headers.Builder result = new Headers.Builder();\n        for (int i = 0, size = requestHeaders.size(); i < size; i++) {\n            String fieldName = requestHeaders.name(i);\n            if (varyFields.contains(fieldName)) {\n                result.add(fieldName, requestHeaders.value(i));\n            }\n        }\n        return result.build();\n    }\n\n    private static Set<String> varyFields(Response response) {\n        return varyFields(response.headers());\n    }\n\n    /**\n     * Returns the names of the request headers that need to be checked for equality when caching.\n     */\n    private static Set<String> varyFields(Headers responseHeaders) {\n        Set<String> result = Collections.emptySet();\n        for (int i = 0, size = responseHeaders.size(); i < size; i++) {\n            if (!\"Vary\".equalsIgnoreCase(responseHeaders.name(i))) continue;\n\n            String value = responseHeaders.value(i);\n            if (result.isEmpty()) {\n                result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);\n            }\n            for (String varyField : value.split(\",\")) {\n                result.add(varyField.trim());\n            }\n        }\n        return result;\n    }\n\n    /**\n     * Returns true if two possibly-null objects are equal.\n     */\n    private static boolean equal(Object a, Object b) {\n        return Objects.equals(a, b);\n    }\n\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/cache/InternalCache.java",
    "content": "package rxhttp.wrapper.cache;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\n\nimport okhttp3.Request;\nimport okhttp3.Response;\n\n/**\n * RxHttp's internal cache interface. Applications shouldn't implement this: instead use {@link CacheManager}.\n */\npublic interface InternalCache {\n    @Nullable\n    Response get(Request request, String key) throws IOException;\n\n    Response put(Response response, String key) throws IOException;\n\n    void remove(String key) throws IOException;\n\n    void removeAll() throws IOException;\n\n    long size() throws IOException;\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/callback/Consumer.java",
    "content": "package rxhttp.wrapper.callback;\n\npublic interface Consumer<T> {\n\n    void accept(T t);\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/callback/Function.java",
    "content": "package rxhttp.wrapper.callback;\n\nimport java.io.IOException;\n\npublic interface Function<T, R> {\n\n    R apply(T t) throws IOException;\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/callback/IConverter.java",
    "content": "package rxhttp.wrapper.callback;\n\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\n\nimport okhttp3.RequestBody;\nimport okhttp3.ResponseBody;\n\n/**\n * User: ljx\n * Date: 2019-11-19\n * Time: 22:54\n */\npublic interface IConverter {\n\n    // ResponseBody convert to T\n    @NotNull\n    <T> T convert(@NotNull ResponseBody body, @NotNull Type type, boolean needDecodeResult) throws IOException;\n\n    // T convert to RequestBody\n    default <T> RequestBody convert(T value) throws IOException {\n        return RequestBody.create(null, new byte[0]);\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/callback/JsonConverter.java",
    "content": "package rxhttp.wrapper.callback;\n\n\nimport okhttp3.MediaType;\n\n/**\n * User: ljx\n * Date: 2021-10-15\n * Time: 22:54\n */\npublic interface JsonConverter extends IConverter {\n\n     MediaType MEDIA_TYPE = MediaType.get(\"application/json; charset=UTF-8\");\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/callback/OutputStreamFactory.kt",
    "content": "package rxhttp.wrapper.callback\n\nimport android.content.Context\nimport android.net.Uri\nimport okhttp3.Response\nimport rxhttp.wrapper.OkHttpCompat\nimport rxhttp.wrapper.entity.ExpandOutputStream\nimport rxhttp.wrapper.utils.isPartialContent\nimport rxhttp.wrapper.utils.length\nimport java.io.File\nimport java.io.IOException\nimport java.net.URLDecoder\n\n/**\n * User: ljx\n * Date: 2020/9/8\n * Time: 22:12\n */\nabstract class OutputStreamFactory<T> {\n\n    // Range headers start index, equivalent to 'Range: bytes=offsetSize-'\n    open fun offsetSize(): Long = 0\n\n    @Throws(IOException::class)\n    abstract fun openOutputStream(response: Response): ExpandOutputStream<T>\n}\n\nabstract class UriFactory(\n    val context: Context\n) : OutputStreamFactory<Uri>() {\n\n    @Throws(IOException::class)\n    abstract fun insert(response: Response): Uri\n\n    open fun query(): Uri? = null\n\n    override fun offsetSize() = query().length(context)\n\n    final override fun openOutputStream(response: Response): ExpandOutputStream<Uri> {\n        return ExpandOutputStream.open(context, insert(response), response.isPartialContent())\n    }\n}\n\nclass UriOutputStreamFactory(\n    private val context: Context,\n    private val uri: Uri\n) : OutputStreamFactory<Uri>() {\n    override fun offsetSize() = uri.length(context)\n\n    override fun openOutputStream(response: Response): ExpandOutputStream<Uri> =\n        ExpandOutputStream.open(context, uri, response.isPartialContent())\n}\n\nclass FileOutputStreamFactory(\n    private val localPath: String\n) : OutputStreamFactory<String>() {\n    override fun offsetSize() = File(localPath).length()\n\n    override fun openOutputStream(response: Response): ExpandOutputStream<String> =\n        File(localPath.replaceSuffix(response)).run {\n            val parentFile = parentFile\n            if (!parentFile.exists() && !parentFile.mkdirs()) {\n                throw IOException(\"Directory $parentFile create fail\")\n            }\n            ExpandOutputStream.open(this, response.isPartialContent())\n        }\n\n    private fun String.replaceSuffix(response: Response): String {\n        return if (endsWith(\"%s\", true) || endsWith(\"%1\\$s\", true)) {\n            val filename = response.findFilename()\n                ?: OkHttpCompat.pathSegments(response).last()\n            format(filename)\n        } else {\n            this\n        }\n    }\n\n    /**\n     * find filename form Content-Disposition response headers\n     * For example:\n     * Content-Disposition: attachment; filename=test.apk\n     * Content-Disposition: attachment; filename='test.apk'\n     * Content-Disposition: attachment; filename=\"test.apk\"\n     * Content-Disposition: attachment;filename*=UTF-8'zh_cn'%E6%B5%8B%E8%AF%95.apk\n     */\n    private fun Response.findFilename(): String? {\n        val header = OkHttpCompat.header(this, \"Content-Disposition\") ?: return null\n        header.split(\";\").forEach {\n            val keyValuePair = it.split(\"=\")\n            if (keyValuePair.size > 1) {\n                return when (keyValuePair[0].trim()) {\n                    \"filename\" -> {\n                        var filename = keyValuePair[1]\n                        //matches \"test.apk\" or 'test.apk'\n                        if (filename.matches(Regex(\"^[\\\"'][\\\\s\\\\S]*[\\\"']\\$\"))) {\n                            filename = filename.substring(1, filename.length - 1)\n                        }\n                        URLDecoder.decode(filename, \"UTF-8\")\n                    }\n                    \"filename*\" -> {\n                        val filename = keyValuePair[1]\n                        val firstIndex = filename.indexOf(\"'\")\n                        val lastIndex = filename.lastIndexOf(\"'\")\n                        if (firstIndex == -1 || lastIndex == -1 || firstIndex >= lastIndex) return null\n                        val charset = filename.substring(0, firstIndex)\n                        URLDecoder.decode(filename.substring(lastIndex + 1), charset)\n                    }\n                    else -> null\n                }\n            }\n        }\n        return null\n    }\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/callback/ProgressCallback.java",
    "content": "package rxhttp.wrapper.callback;\n\n\n/**\n * User: ljx\n * Date: 2017/12/1\n * Time: 20:22\n */\npublic interface ProgressCallback {\n\n    void onProgress(long currentSize, long totalSize, long speed);\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/callback/ProgressCallbackHelper.kt",
    "content": "package rxhttp.wrapper.callback\n\nimport android.os.SystemClock\nimport rxhttp.wrapper.utils.Speeder\n\n\n/**\n * User: ljx\n * Date: 2024/6/17\n * Time: 11:31\n */\nclass ProgressCallbackHelper(\n    //上传/下载进度回调最小周期, 值越小，回调事件越多，设置一个合理值，可避免密集回调\n    private val minPeriod: Int,\n    private var callback: ProgressCallback\n) {\n    private var currentLength = 0L\n    private var lastTime = 0L\n\n    private lateinit var speeder: Speeder\n\n    fun onStart(offSize: Long) {\n        currentLength = offSize\n        lastTime = SystemClock.elapsedRealtime()\n        speeder = Speeder(offSize, lastTime)\n    }\n\n    /**\n     * @param onceByteLength 单次上传/下载的字节长度，有可能为-1，下载总长度未知时，下载完成会传入-1\n     * @param contentLength 上传/下载的总长度, 有可能为-1，代表总长度未知，下载时，有可能拿不到总长度\n     */\n    fun onProgress(onceByteLength: Long, contentLength: Long) {\n        if (onceByteLength != -1L) {\n            currentLength += onceByteLength\n        }\n        val completed = currentLength == contentLength || onceByteLength == -1L  //上传/下载是否完成\n        val currentTime = SystemClock.elapsedRealtime()\n        if (currentTime - lastTime >= minPeriod || completed) {\n            val averageSpeed = speeder.updateSpeed(currentLength, currentTime, completed)\n            val totalSize = if (completed) currentLength else contentLength\n            callback.onProgress(currentLength, totalSize, averageSpeed)\n            lastTime = currentTime\n        }\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/converter/GsonConverter.java",
    "content": "package rxhttp.wrapper.converter;\n\n\nimport com.google.gson.Gson;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.reflect.TypeToken;\nimport com.google.gson.stream.JsonWriter;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.io.OutputStreamWriter;\nimport java.io.Writer;\nimport java.lang.reflect.Type;\n\nimport kotlin.text.Charsets;\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okhttp3.ResponseBody;\nimport okio.Buffer;\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.wrapper.callback.JsonConverter;\nimport rxhttp.wrapper.utils.GsonUtil;\n\n/**\n * RxHttp 默认的转换器，通过Gson转换数据\n * User: ljx\n * Date: 2019-11-21\n * Time: 22:19\n */\npublic class GsonConverter implements JsonConverter {\n\n    private final Gson gson;\n    private final MediaType contentType;\n\n    private GsonConverter(Gson gson, MediaType contentType) {\n        this.gson = gson;\n        this.contentType = contentType;\n    }\n\n    public static GsonConverter create() {\n        return create(GsonUtil.buildGson());\n    }\n\n    public static GsonConverter create(Gson gson) {\n        return create(gson, JsonConverter.MEDIA_TYPE);\n    }\n\n    public static GsonConverter create(Gson gson, MediaType contentType) {\n        if (gson == null) throw new NullPointerException(\"gson == null\");\n        return new GsonConverter(gson, contentType);\n    }\n\n    @NotNull\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public <T> T convert(@NotNull ResponseBody body, @NotNull Type type, boolean needDecodeResult) throws IOException {\n        try {\n            T t;\n            if (needDecodeResult || type == String.class) {\n                String result = body.string();\n                if (needDecodeResult) {\n                    result = RxHttpPlugins.onResultDecoder(result);\n                }\n                if (type == String.class) {\n                    return (T) result;\n                }\n                t = gson.fromJson(result, type);\n            } else {\n                t = gson.fromJson(body.charStream(), type);\n            }\n            if (t == null) {\n                throw new IllegalStateException(\"GsonConverter Could not deserialize body as \" + type);\n            }\n            return t;\n        } finally {\n            body.close();\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public <T> RequestBody convert(T value) throws IOException {\n        TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(value.getClass());\n        TypeAdapter<T> adapter = this.gson.getAdapter(typeToken);\n        Buffer buffer = new Buffer();\n        Writer writer = new OutputStreamWriter(buffer.outputStream(), Charsets.UTF_8);\n        JsonWriter jsonWriter = gson.newJsonWriter(writer);\n        adapter.write(jsonWriter, value);\n        jsonWriter.close();\n        return RequestBody.create(contentType, buffer.readByteString());\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/cookie/CookieStore.java",
    "content": "package rxhttp.wrapper.cookie;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport okhttp3.Cookie;\nimport okhttp3.HttpUrl;\nimport okhttp3.internal.cache.DiskLruCache;\nimport okio.BufferedSink;\nimport okio.BufferedSource;\nimport okio.ByteString;\nimport okio.Okio;\nimport okio.Source;\nimport rxhttp.wrapper.OkHttpCompat;\n\n/**\n * Cookie管理类，支持内存、磁盘同时缓存，默认仅开启内存缓存；若要开启磁盘缓存，构造方法传入磁盘缓存目录即可\n * <p>\n * 注：内存、磁盘缓存至少要开启一个，否则构造方法抛出非法参数异常\n * User: ljx\n * Date: 2019-12-29\n * Time: 21:51\n */\npublic class CookieStore implements ICookieJar {\n\n    private static final int appVersion = 1;\n\n    private final File directory;\n    private final long maxSize;\n    private DiskLruCache diskCache; //磁盘缓存\n    private Map<String, ConcurrentHashMap<String, Cookie>> memoryCache; //内存缓存\n\n    public CookieStore() {\n        this(null, Integer.MAX_VALUE, true);\n    }\n\n    public CookieStore(@Nullable File directory) {\n        this(directory, Integer.MAX_VALUE, true);\n    }\n\n    public CookieStore(@Nullable File directory, boolean enabledMemory) {\n        this(directory, Integer.MAX_VALUE, enabledMemory);\n    }\n\n    /**\n     * 配置cookie 存储策略，注意：内存缓存、磁盘缓存至少要开启一个，否则抛出非法参数异常\n     *\n     * @param directory     磁盘缓存目录，传入null，则代表不开启磁盘缓存\n     * @param maxSize       磁盘缓存最大size，默认为 Integer.MAX_VALUE\n     * @param enabledMemory 是否开启内存缓存\n     */\n    public CookieStore(@Nullable File directory, long maxSize, boolean enabledMemory) {\n        if (!enabledMemory && directory == null)\n            throw new IllegalArgumentException(\"Memory or disk caching must be enabled\");\n        if (enabledMemory) {\n            memoryCache = new ConcurrentHashMap<>();\n        }\n        this.directory = directory;\n        this.maxSize = maxSize;\n\n    }\n\n    private DiskLruCache getDiskLruCache() {\n        if (directory != null && diskCache == null) {\n            diskCache = OkHttpCompat.newDiskLruCache(directory, appVersion, 1, maxSize);\n        }\n        return diskCache;\n    }\n\n    /**\n     * 保存url对应的cookie，线程安全，若开启了磁盘缓存，建议在子线程调用\n     *\n     * @param url    HttpUrl\n     * @param cookie Cookie\n     */\n    @Override\n    public void saveCookie(HttpUrl url, Cookie cookie) {\n        List<Cookie> cookies = new ArrayList<>();\n        cookies.add(cookie);\n        saveCookie(url, cookies);\n    }\n\n    /**\n     * 保存url对应的所有cookie，线程安全，若开启了磁盘缓存，建议在子线程调用\n     *\n     * @param url     HttpUrl\n     * @param cookies List\n     */\n    @Override\n    public void saveCookie(HttpUrl url, List<Cookie> cookies) {\n        final String host = url.host();\n        ConcurrentHashMap<String, Cookie> cookieMap;\n        if (memoryCache != null) { //开启了内存缓存，则将cookie写入内存\n            cookieMap = memoryCache.get(host);\n            if (cookieMap == null) {\n                memoryCache.put(host, cookieMap = new ConcurrentHashMap<>());\n            }\n        } else {\n            cookieMap = new ConcurrentHashMap<>();\n        }\n        for (Cookie cookie : cookies) {\n            cookieMap.put(getToken(cookie), cookie);\n        }\n\n        DiskLruCache diskCache = getDiskLruCache();\n        if (diskCache != null) { //开启了磁盘缓存，则将cookie写入磁盘\n            DiskLruCache.Editor editor = null;\n            try {\n                editor = diskCache.edit(md5(host));\n                if (editor == null) {\n                    return;\n                }\n                writeCookie(editor, cookieMap);\n                editor.commit();\n            } catch (Exception e) {\n                e.printStackTrace();\n            } finally {\n                abortQuietly(editor);\n            }\n        }\n    }\n\n    /**\n     * 加载url对应的cookie，线程安全，若开启了磁盘缓存，建议在子线程调用\n     *\n     * @param url HttpUrl\n     * @return List\n     */\n    @Override\n    public List<Cookie> loadCookie(HttpUrl url) {\n        final String host = url.host();\n        if (memoryCache != null) {    //1、开启了内存缓存，则从内存查找cookie\n            Map<String, Cookie> cookieMap = memoryCache.get(host);\n            if (cookieMap != null) {  //2、内存缓存查找成功，直接返回\n                return matchCookies(url, cookieMap);\n            }\n        }\n        ConcurrentHashMap<String, Cookie> cookieMap = new ConcurrentHashMap<>();\n        DiskLruCache diskCache = getDiskLruCache();\n        if (diskCache != null) { //3、开启了磁盘缓存，则从磁盘查找cookie\n            DiskLruCache.Snapshot snapshot = null;\n            try {\n                //4、磁盘缓存查找\n                snapshot = diskCache.get(md5(host));\n                if (snapshot == null) return Collections.emptyList();\n                List<Cookie> cookiesList = readCookie(url, snapshot.getSource(0));\n                for (Cookie cookie : cookiesList) {\n                    cookieMap.put(getToken(cookie), cookie);\n                }\n            } catch (Exception e) {\n                e.printStackTrace();\n            } finally {\n                OkHttpCompat.closeQuietly(snapshot);\n            }\n        }\n        if (memoryCache != null && !cookieMap.isEmpty()) //5、磁盘缓存查找成功，添加进内存缓存\n            memoryCache.put(host, cookieMap);\n        return matchCookies(url, cookieMap);\n    }\n\n    private List<Cookie> matchCookies(HttpUrl url, Map<String, Cookie> cookieMap) {\n        List<Cookie> matchCookies = new ArrayList<>();\n        for (Cookie cookie : cookieMap.values()) {\n            //cookie匹配且未过期\n            if (cookie.matches(url) && cookie.expiresAt() > System.currentTimeMillis()) {\n                matchCookies.add(cookie);\n            }\n        }\n        return Collections.unmodifiableList(matchCookies);\n    }\n\n\n    private String getToken(Cookie cookie) {\n        return cookie.name() + \"; \" + cookie.domain() + \"; \" + cookie.path() + \"; \" + cookie.secure();\n    }\n\n    /**\n     * 移除url对应的cookie，线程安全，若开启了磁盘缓存，建议在子线程调用\n     *\n     * @param url HttpUrl\n     */\n    @Override\n    public void removeCookie(HttpUrl url) {\n        String host = url.host();\n        if (memoryCache != null) {\n            memoryCache.remove(host);\n        }\n        DiskLruCache diskCache = getDiskLruCache();\n        if (diskCache != null) {\n            try {\n                diskCache.remove(md5(host));\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * 移除所有的cookie，线程安全，若开启了磁盘缓存，建议在子线程调用\n     */\n    @Override\n    public void removeAllCookie() {\n        if (memoryCache != null)\n            memoryCache.clear();\n        DiskLruCache diskCache = getDiskLruCache();\n        if (diskCache != null) {\n            try {\n                diskCache.evictAll();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    //从磁盘都cookie\n    private List<Cookie> readCookie(HttpUrl url, Source in) throws IOException {\n        List<Cookie> cookies = new ArrayList<>();\n        try {\n            BufferedSource source = Okio.buffer(in);\n            int size = source.readInt();\n            for (int i = 0; i < size; i++) {\n                String name = source.readUtf8LineStrict();\n                cookies.add(Cookie.parse(url, name));\n            }\n        } finally {\n            in.close();\n        }\n        return cookies;\n    }\n\n    //cookie写入磁盘\n    private void writeCookie(DiskLruCache.Editor editor, Map<String, Cookie> cookieMap) throws\n        IOException {\n        BufferedSink sink = Okio.buffer(editor.newSink(0));\n        sink.writeInt(cookieMap.size());\n        for (Cookie cookie : cookieMap.values()) {\n            sink.writeUtf8(cookie.toString()).writeByte('\\n');\n        }\n        sink.close();\n    }\n\n    private void abortQuietly(@Nullable DiskLruCache.Editor editor) {\n        try {\n            if (editor != null) {\n                editor.abort();\n            }\n        } catch (Exception ignored) {\n        }\n    }\n\n    private static String md5(String key) {\n        return ByteString.encodeUtf8(key).md5().hex();\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/cookie/ICookieJar.java",
    "content": "package rxhttp.wrapper.cookie;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.List;\n\nimport okhttp3.Cookie;\nimport okhttp3.CookieJar;\nimport okhttp3.HttpUrl;\n\n/**\n * User: ljx\n * Date: 2019-12-29\n * Time: 20:47\n */\npublic interface ICookieJar extends CookieJar {\n\n    @Override\n    default void saveFromResponse(@NotNull HttpUrl url, @NotNull List<Cookie> cookies) {\n        saveCookie(url, cookies);\n    }\n\n    @Override\n    default List<Cookie> loadForRequest(@NotNull HttpUrl url) {\n        return loadCookie(url);\n    }\n\n    /**\n     * 保存url对应所有cookie\n     * @param url  HttpUrl\n     * @param cookies List\n     */\n    void saveCookie(HttpUrl url, List<Cookie> cookies);\n\n    /**\n     * 保存url对应所有cookie\n     * @param url HttpUrl\n     * @param cookie Cookie\n     */\n    void saveCookie(HttpUrl url, Cookie cookie);\n\n    /**\n     * 加载url所有的cookie\n     * @param url HttpUrl\n     * @return List\n     */\n    List<Cookie> loadCookie(HttpUrl url);\n\n    /**\n     * 移除url 对应的cookie\n     * @param url HttpUrl\n     */\n    void removeCookie(HttpUrl url);\n\n    /**\n     * 移除所有cookie\n     */\n    void removeAllCookie();\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/coroutines/Await.kt",
    "content": "package rxhttp.wrapper.coroutines\n\n/**\n * User: ljx\n * Date: 2020/3/21\n * Time: 17:06\n */\ninterface Await<T> {\n\n    suspend fun await(): T\n}\n\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/coroutines/CallAwait.kt",
    "content": "package rxhttp.wrapper.coroutines\n\nimport okhttp3.Call\nimport rxhttp.wrapper.CallFactory\nimport rxhttp.wrapper.entity.OkResponse\nimport rxhttp.wrapper.parse.OkResponseParser\nimport rxhttp.wrapper.parse.Parser\nimport rxhttp.wrapper.utils.LogUtil\nimport rxhttp.wrapper.utils.await\n\n/**\n * User: ljx\n * Date: 2020/3/21\n * Time: 17:06\n */\nclass CallAwait<T>(\n    private val callFactory: CallFactory,\n    private val parser: Parser<T>,\n) : Await<T> {\n\n    fun toAwaitOkResponse(): CallAwait<OkResponse<T?>> =\n        CallAwait(callFactory, OkResponseParser(parser))\n\n    override suspend fun await(): T {\n        var call: Call? = null\n        return try {\n            call = callFactory.newCall()\n            call.await(parser)\n        } catch (e: Throwable) {\n            LogUtil.logCall(call, e)\n            throw e\n        }\n    }\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/coroutines/CallFlow.kt",
    "content": "package rxhttp.wrapper.coroutines\n\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.channels.BufferOverflow\nimport kotlinx.coroutines.flow.AbstractFlow\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.flow.buffer\nimport kotlinx.coroutines.flow.channelFlow\nimport kotlinx.coroutines.flow.mapNotNull\nimport rxhttp.toAwait\nimport rxhttp.wrapper.BodyParamFactory\nimport rxhttp.wrapper.CallFactory\nimport rxhttp.wrapper.callback.ProgressCallback\nimport rxhttp.wrapper.entity.OkResponse\nimport rxhttp.wrapper.entity.Progress\nimport rxhttp.wrapper.parse.OkResponseParser\nimport rxhttp.wrapper.parse.Parser\nimport rxhttp.wrapper.parse.StreamParser\n\n/**\n * User: ljx\n * Date: 2023/5/4\n * Time: 15:49\n */\n@OptIn(ExperimentalCoroutinesApi::class)\nclass CallFlow<T>(\n    private val callFactory: CallFactory,\n    private val parser: Parser<T>\n) : AbstractFlow<T>() {\n\n    override suspend fun collectSafely(collector: FlowCollector<T>) {\n        val await = callFactory.toAwait(parser)\n        collector.emit(await.await())\n    }\n\n    fun toFlowOkResponse(): CallFlow<OkResponse<T?>> =\n        CallFlow(callFactory, OkResponseParser(parser))\n\n    /**\n     * @param capacity 进度回调队列容量,生产速度大于消费速度时,事件会堆积在队列里,当堆积数量超出队列容量时，会丢弃旧的事件\n     * 如果想要更多的进度回调事件,可设置一个相对较大的容量\n     * @param minPeriod 上传/下载进度回调最小周期，必须大于0，默认500毫秒\n     * @param progress 进度回调\n     */\n    fun onProgress(capacity: Int = 2, minPeriod: Int = 500, progress: suspend (Progress<T>) -> Unit): Flow<T> =\n        toFlowProgress(capacity, minPeriod)\n            .mapNotNull {\n                if (it.result == null) progress(it)\n                it.result\n            }\n\n    fun toFlowProgress(capacity: Int = 2, minPeriod: Int = 500): Flow<Progress<T>> {\n        require(capacity in 2..100) { \"capacity must be in [2..100], but it was $capacity\" }\n        require(minPeriod > 0) { \"minPeriod must be between 0 and Int.MAX_VALUE, but it was $minPeriod\" }\n        var streamParser: Parser<*> = parser\n        while (streamParser is OkResponseParser<*>) {\n            streamParser = streamParser.parser\n        }\n        if (streamParser !is StreamParser && callFactory !is BodyParamFactory) {\n            throw UnsupportedOperationException(\"parser is \" + streamParser.javaClass.name + \", callFactory is \" + callFactory.javaClass.name)\n        }\n        return channelFlow {\n            val progressCallback = ProgressCallback { currentSize, totalSize, speed ->\n                trySend(Progress<T>(currentSize, totalSize, speed))\n            }\n            if (streamParser is StreamParser) {\n                streamParser.setProgressCallback(minPeriod, progressCallback)\n            } else if (callFactory is BodyParamFactory) {\n                callFactory.param.setProgressCallback(minPeriod, progressCallback)\n            }\n            val t: T = callFactory.toAwait(parser).await()\n            trySend(Progress(t))\n        }.buffer(capacity, BufferOverflow.DROP_OLDEST)\n    }\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/entity/DownloadOffSize.java",
    "content": "package rxhttp.wrapper.entity;\n\n/**\n * User: ljx\n * Date: 2022/9/27\n * Time: 17:13\n */\npublic class DownloadOffSize {\n\n    public final long offSize;\n\n    public DownloadOffSize(long offSize) {\n        this.offSize = offSize;\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/entity/EmptyResponseBody.java",
    "content": "package rxhttp.wrapper.entity;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport okhttp3.MediaType;\nimport okhttp3.ResponseBody;\nimport okio.BufferedSource;\n\n/**\n * User: ljx\n * Date: 2022/9/5\n * Time: 15:14\n */\npublic final class EmptyResponseBody extends ResponseBody {\n    private final @Nullable MediaType contentType;\n    private final long contentLength;\n\n    public EmptyResponseBody(@Nullable MediaType contentType, long contentLength) {\n        this.contentType = contentType;\n        this.contentLength = contentLength;\n    }\n\n    @Override\n    public MediaType contentType() {\n        return contentType;\n    }\n\n    @Override\n    public long contentLength() {\n        return contentLength;\n    }\n\n    @Override\n    public BufferedSource source() {\n        throw new IllegalStateException(\"Cannot read raw response body of a converted body.\");\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/entity/ExpandOutputStream.java",
    "content": "package rxhttp.wrapper.entity;\n\nimport android.content.Context;\nimport android.net.Uri;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * User: ljx\n * Date: 2022/9/27\n * Time: 17:16\n */\npublic final class ExpandOutputStream<T> extends OutputStream {\n\n    private final T expand;\n    private final OutputStream os;\n\n    public ExpandOutputStream(T expand, OutputStream os) {\n        this.expand = expand;\n        this.os = os;\n    }\n\n    public static ExpandOutputStream<String> open(File file, boolean append) throws FileNotFoundException {\n        return new ExpandOutputStream<>(file.getAbsolutePath(), new FileOutputStream(file, append));\n    }\n\n    public static ExpandOutputStream<Uri> open(Context context, Uri uri, boolean append) throws FileNotFoundException {\n        OutputStream os = context.getContentResolver().openOutputStream(uri, append ? \"wa\" : \"w\");\n        return new ExpandOutputStream<>(uri, os);\n    }\n\n    public T getExpand() {\n        return expand;\n    }\n\n    @Override\n    public void write(int b) throws IOException {\n        os.write(b);\n    }\n\n    @Override\n    public void write(@NotNull byte[] b) throws IOException {\n        os.write(b);\n    }\n\n    @Override\n    public void write(@NotNull byte[] b, int off, int len) throws IOException {\n        os.write(b, off, len);\n    }\n\n    @Override\n    public void close() throws IOException {\n        os.close();\n    }\n\n    @Override\n    public void flush() throws IOException {\n        os.flush();\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/entity/FileRequestBody.java",
    "content": "package rxhttp.wrapper.entity;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okio.BufferedSink;\nimport okio.Okio;\nimport okio.Source;\nimport rxhttp.wrapper.OkHttpCompat;\n\n/**\n * For compatibility with okHTTP 3.x version, only written in Java\n * User: ljx\n * Date: 2021/6/24\n * Time: 21:13\n */\npublic class FileRequestBody extends RequestBody {\n\n    private final File file;\n    private final long skipSize;\n    private final MediaType mediaType;\n\n    public FileRequestBody(File file, long skipSize, @Nullable MediaType mediaType) {\n        this.file = file;\n        if (skipSize < 0) {\n            throw new IllegalArgumentException(\"skipSize >= 0 required but it was \" + skipSize);\n        }\n        if (skipSize > file.length()) {\n            throw new IllegalArgumentException(\"skipSize cannot be larger than the file length. \" +\n                \"The file length is \" + file.length() + \", but it was \" + skipSize);\n        }\n        this.skipSize = skipSize;\n        this.mediaType = mediaType;\n    }\n\n    @Override\n    public MediaType contentType() {\n        return mediaType;\n    }\n\n    @Override\n    public long contentLength() throws IOException {\n        return file.length() - skipSize;\n    }\n\n    @Override\n    public void writeTo(@NotNull BufferedSink sink) throws IOException {\n        InputStream input = null;\n        Source source = null;\n        try {\n            input = new FileInputStream(file);\n            if (skipSize > 0) {\n                long skip = input.skip(skipSize);\n                if (skip != skipSize) {\n                    throw new IllegalArgumentException(\n                        \"Expected to skip \" + skipSize + \" bytes, actually skipped \" + skip + \" bytes\");\n                }\n            }\n            source = Okio.source(input);\n            sink.writeAll(source);\n        } finally {\n            OkHttpCompat.closeQuietly(source, input);\n        }\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/entity/KeyValuePair.java",
    "content": "package rxhttp.wrapper.entity;\n\n\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * User: ljx\n * Date: 2019-11-15\n * Time: 22:44\n */\npublic class KeyValuePair {\n\n    private final String key;\n    private final Object value;\n    private final boolean isEncoded;\n\n    public KeyValuePair(String key, @Nullable Object object) {\n        this(key, object, false);\n    }\n\n    public KeyValuePair(String key, @Nullable Object value, boolean isEncoded) {\n        this.key = key;\n        this.value = value;\n        this.isEncoded = isEncoded;\n    }\n\n    public String getKey() {\n        return key;\n    }\n\n    public Object getValue() {\n        return value;\n    }\n\n    public boolean isEncoded() {\n        return isEncoded;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == this) return true;\n        if (!(obj instanceof KeyValuePair)) return false;\n        KeyValuePair keyValuePair = (KeyValuePair) obj;\n        return keyValuePair.getKey().equals(getKey());\n    }\n\n    @Override\n    public int hashCode() {\n        return key.hashCode();\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/entity/OkResponse.java",
    "content": "package rxhttp.wrapper.entity;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Objects;\n\nimport okhttp3.Headers;\nimport okhttp3.ResponseBody;\n\n/**\n * User: ljx\n * Date: 2022/9/5\n * Time: 15:14\n */\npublic final class OkResponse<T> {\n\n    private final okhttp3.Response rawResponse;\n    private final @Nullable T body;\n    private final @Nullable ResponseBody errorBody;\n\n    private OkResponse(\n        okhttp3.Response rawResponse, @Nullable T body, @Nullable ResponseBody errorBody) {\n        this.rawResponse = rawResponse;\n        this.body = body;\n        this.errorBody = errorBody;\n    }\n\n    /**\n     * Create a successful response from {@code rawResponse} with {@code body} as the deserialized\n     * body.\n     */\n    public static <T> OkResponse<T> success(@Nullable T body, okhttp3.Response rawResponse) {\n        Objects.requireNonNull(rawResponse, \"rawResponse == null\");\n        if (!rawResponse.isSuccessful()) {\n            throw new IllegalArgumentException(\"rawResponse must be successful response\");\n        }\n        return new OkResponse<>(rawResponse, body, null);\n    }\n\n    /**\n     * Create an error response from {@code rawResponse} with {@code body} as the error body.\n     */\n    public static <T> OkResponse<T> error(ResponseBody body, okhttp3.Response rawResponse) {\n        Objects.requireNonNull(body, \"body == null\");\n        Objects.requireNonNull(rawResponse, \"rawResponse == null\");\n        if (rawResponse.isSuccessful()) {\n            throw new IllegalArgumentException(\"rawResponse should not be successful response\");\n        }\n        return new OkResponse<>(rawResponse, null, body);\n    }\n\n    /**\n     * The raw response from the HTTP client.\n     */\n    public okhttp3.Response raw() {\n        return rawResponse;\n    }\n\n    /**\n     * HTTP status code.\n     */\n    public int code() {\n        return rawResponse.code();\n    }\n\n    /**\n     * HTTP status message or null if unknown.\n     */\n    public String message() {\n        return rawResponse.message();\n    }\n\n    /**\n     * HTTP headers.\n     */\n    public Headers headers() {\n        return rawResponse.headers();\n    }\n\n    /**\n     * Returns true if {@link #code()} is in the range [200..300).\n     */\n    public boolean isSuccessful() {\n        return rawResponse.isSuccessful();\n    }\n\n    /**\n     * The deserialized response body of a {@linkplain #isSuccessful() successful} response.\n     */\n    @Nullable\n    public T body() {\n        return body;\n    }\n\n    /**\n     * The raw response body of an {@linkplain #isSuccessful() unsuccessful} response.\n     */\n    @Nullable\n    public ResponseBody errorBody() {\n        return errorBody;\n    }\n\n    @Override\n    public String toString() {\n        return rawResponse.toString();\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/entity/ParameterizedTypeImpl.java",
    "content": "package rxhttp.wrapper.entity;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\n\nimport rxhttp.wrapper.utils.Utils;\n\n\n/**\n * User: ljx\n * Date: 2022/9/27\n * Time: 17:28\n */\npublic class ParameterizedTypeImpl implements ParameterizedType {\n\n    private final Type rawType;\n    private final Type ownerType;\n    private final Type[] actualTypeArguments;\n\n    public ParameterizedTypeImpl(Type rawType, Type actualType) {\n        this(null, rawType, actualType);\n    }\n\n    public ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type... actualTypeArguments) {\n        this.rawType = rawType;\n        this.ownerType = ownerType;\n        this.actualTypeArguments = actualTypeArguments;\n    }\n\n    // get(List.class, List.class, String.class) equivalent to List<List<String>>\n    public static ParameterizedType get(@NotNull Type rawType, @NotNull Type... types) {\n        final int length = types.length;\n        Type lastType = Utils.getWrapType(types[length - 1]);\n        for (int i = length - 2; i >= 0; i--) {\n            lastType = new ParameterizedTypeImpl(types[i], lastType);\n        }\n        return new ParameterizedTypeImpl(rawType, lastType);\n    }\n\n    // getParameterized(List.Class, String.class) equivalent to List<String>\n    // getParameterized(Map.Class, String.class, Integer.class) equivalent to Map<String, Integer>\n    public static ParameterizedType getParameterized(@NotNull Type rawType, @NotNull Type... types) {\n        final int length = types.length;\n        Type[] newTypes = new Type[length];\n        for (int i = 0; i < length; i++) {\n            newTypes[i] = Utils.getWrapType(types[i]);\n        }\n        return new ParameterizedTypeImpl(null, rawType, newTypes);\n    }\n\n    @Override\n    public final Type[] getActualTypeArguments() {\n        return actualTypeArguments;\n    }\n\n    @Override\n    public final Type getOwnerType() {\n        return ownerType;\n    }\n\n    @Override\n    public final Type getRawType() {\n        return rawType;\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/entity/Progress.java",
    "content": "package rxhttp.wrapper.entity;\n\nimport org.jetbrains.annotations.Nullable;\n\n/**\n * User: ljx\n * Date: 2019/1/20\n * Time: 18:15\n */\npublic class Progress<T> {\n\n    private final long currentSize;//当前已完成的字节大小\n    private final long totalSize; //总字节大小\n    private final long speed;  //网速, 1秒更新一次 单位: byte/s\n\n    @Nullable\n    private T result; //http返回结果,上传/下载完成时才有值\n\n    public Progress(@Nullable T result) {\n        this(0, 0, 0);\n        this.result = result;\n    }\n\n    public Progress(long currentSize, long totalSize, long speed) {\n        this.currentSize = currentSize;\n        this.totalSize = totalSize;\n        this.speed = speed;\n    }\n\n    @Nullable\n    public T getResult() {\n        return result;\n    }\n\n    //返回上传/下载速度，单位: byte/s\n    public long getSpeed() {\n        return speed;\n    }\n\n    //根据当前速度计算，上传/下载剩余时间，单位: s (注：剩余时间是不精准的)\n    public long calculateRemainingTime() {\n        if (totalSize == -1) return -1;\n        long remainingSize = totalSize - currentSize;\n        if (remainingSize == 0) return 0;\n        if (speed == 0) return -1;\n        long remainingTime = remainingSize / speed;\n        if (remainingSize % speed > 0) remainingTime++;\n        return remainingTime;\n    }\n\n    //return [0, 100]\n    public int getProgress() {\n        return (int) (getFraction() * 100);\n    }\n\n    //return [0.0, 1.0]\n    public float getFraction() {\n        if (totalSize == -1) return 0.0f;\n        if (totalSize < 0) {\n            throw new IllegalArgumentException(\"totalSize must be greater than 0, but it was \" + totalSize);\n        }\n        if (currentSize > totalSize) {\n            throw new IllegalArgumentException(\"totalSize can't be greater than totalSize, \" + \"currentSize=\" + currentSize + \" totalSize=\" + totalSize);\n        }\n        return currentSize * 1.0f / totalSize;\n    }\n\n    public long getCurrentSize() {\n        return currentSize;\n    }\n\n    public long getTotalSize() {\n        return totalSize;\n    }\n\n    @Override\n    public String toString() {\n        return \"Progress{\" + \"fraction=\" + getFraction() + \", currentSize=\" + currentSize + \", totalSize=\" + totalSize + \", speed=\" + speed + '}';\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/entity/UpFile.java",
    "content": "package rxhttp.wrapper.entity;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.File;\n\n/**\n * User: ljx\n * Date: 2022/9/27\n * Time: 18:54\n */\npublic class UpFile {\n\n    private final String key;\n    private final File file;\n    private final String filename;\n    private final long skipSize;\n\n    public UpFile(String key, String path) {\n        this(key, new File(path));\n    }\n\n    public UpFile(String key, File file) {\n        this(key, file, file.getName());\n    }\n\n    public UpFile(String key, File file, String filename) {\n        this(key, file, filename, 0);\n    }\n\n    public UpFile(@NotNull String key, @NotNull File file, @Nullable String filename, long skipSize) {\n        this.key = key;\n        this.file = file;\n        this.filename = filename;\n        this.skipSize = skipSize;\n    }\n\n    public String getKey() {\n        return key;\n    }\n\n    public File getFile() {\n        return file;\n    }\n\n    public String getFilename() {\n        return filename;\n    }\n\n    public long getSkipSize() {\n        return skipSize;\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/entity/UriRequestBody.java",
    "content": "package rxhttp.wrapper.entity;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.net.Uri;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okio.BufferedSink;\nimport okio.Okio;\nimport okio.Source;\nimport rxhttp.wrapper.OkHttpCompat;\nimport rxhttp.wrapper.utils.UriUtil;\n\n/**\n * For compatibility with okHTTP 3.x version, only written in Java\n * User: ljx\n * Date: 2020/9/13\n * Time: 21:13\n */\npublic class UriRequestBody extends RequestBody {\n\n    private final Uri uri;\n    private final long skipSize;\n    private final MediaType contentType;\n    private final ContentResolver contentResolver;\n\n    public UriRequestBody(Context context, Uri uri, long skipSize, @Nullable MediaType contentType) {\n        this.uri = uri;\n        if (skipSize < 0) {\n            throw new IllegalArgumentException(\"skipSize >= 0 required but it was \" + skipSize);\n        }\n        this.skipSize = skipSize;\n        this.contentType = contentType;\n        contentResolver = context.getContentResolver();\n    }\n\n    @Override\n    public MediaType contentType() {\n        return contentType;\n    }\n\n    @Override\n    public long contentLength() throws IOException {\n        long fileLength = UriUtil.length(uri, contentResolver);\n        if (skipSize > 0 && skipSize > fileLength) {\n            throw new IllegalArgumentException(\"skipSize cannot be larger than the file length. \" +\n                \"The file length is \" + fileLength + \", but it was \" + skipSize);\n        }\n        return fileLength - skipSize;\n    }\n\n    @Override\n    public void writeTo(@NotNull BufferedSink sink) throws IOException {\n        InputStream input = null;\n        Source source = null;\n        try {\n            input = contentResolver.openInputStream(uri);\n            if (skipSize > 0) {\n                long skip = input.skip(skipSize);\n                if (skip != skipSize) {\n                    throw new IllegalArgumentException(\n                        \"Expected to skip \" + skipSize + \" bytes, actually skipped \" + skip + \" bytes\");\n                }\n            }\n            source = Okio.source(input);\n            sink.writeAll(source);\n        } finally {\n            OkHttpCompat.closeQuietly(source, input);\n        }\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/exception/CacheReadFailedException.java",
    "content": "package rxhttp.wrapper.exception;\n\nimport java.io.IOException;\n\nimport rxhttp.wrapper.cache.CacheMode;\n\n/**\n * 缓存读取失败异常，仅在 {@link CacheMode#ONLY_CACHE}模式下会抛出\n * User: ljx\n * Date: 2019-12-15\n * Time: 20:16\n */\npublic class CacheReadFailedException extends IOException {\n\n    public CacheReadFailedException(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/exception/HttpStatusCodeException.java",
    "content": "package rxhttp.wrapper.exception;\n\nimport java.io.IOException;\n\nimport okhttp3.Headers;\nimport okhttp3.HttpUrl;\nimport okhttp3.Protocol;\nimport okhttp3.Request;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\n\n/**\n * Http 状态码 code < 200 || code >= 300, 抛出此异常\n * <p>\n * 可通过{@link #getLocalizedMessage()}方法获取code\n * User: ljx\n * Date: 2019-06-09\n * Time: 09:56\n */\npublic final class HttpStatusCodeException extends IOException {\n\n    private final Protocol protocol; //http协议\n    private final int statusCode; //Http响应状态吗\n    private final String requestMethod; //请求方法，Get/Post等\n    private final HttpUrl httpUrl; //请求Url及查询参数\n    private final Headers responseHeaders; //响应头\n    private final ResponseBody body;\n    private String result;    //返回结果\n\n    public HttpStatusCodeException(Response response) {\n        super(response.message());\n        protocol = response.protocol();\n        statusCode = response.code();\n        Request request = response.request();\n        requestMethod = request.method();\n        httpUrl = request.url();\n        responseHeaders = response.headers();\n        body = response.body();\n    }\n\n    @Override\n    public String getLocalizedMessage() {\n        return String.valueOf(statusCode);\n    }\n\n    public int getStatusCode() {\n        return statusCode;\n    }\n\n    public String getRequestMethod() {\n        return requestMethod;\n    }\n\n    public String getRequestUrl() {\n        return httpUrl.toString();\n    }\n\n    public HttpUrl getHttpUrl() {\n        return httpUrl;\n    }\n\n    public Headers getResponseHeaders() {\n        return responseHeaders;\n    }\n\n    public ResponseBody getResponseBody() {\n        return body;\n    }\n\n    public String getResult() throws IOException {\n        if (result == null) {\n            result = body.string();\n        }\n        return result;\n    }\n\n    @Override\n    public String toString() {\n        return getClass().getName() + \": \" + protocol + \" \" + statusCode + \" \" + getMessage();\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/exception/ParseException.java",
    "content": "package rxhttp.wrapper.exception;\n\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\n\nimport okhttp3.Headers;\nimport okhttp3.HttpUrl;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\n/**\n * User: ljx\n * Date: 2018/10/23\n * Time: 22:29\n */\npublic class ParseException extends IOException {\n\n    private final String errorCode;\n\n    private final String requestMethod; //请求方法，Get/Post等\n    private final HttpUrl httpUrl; //请求Url及查询参数\n    private final Headers responseHeaders; //响应头\n\n    public ParseException(@NotNull String code, String message, Response response) {\n        super(message);\n        errorCode = code;\n\n        Request request = response.request();\n        requestMethod = request.method();\n        httpUrl = request.url();\n        responseHeaders = response.headers();\n    }\n\n    public String getErrorCode() {\n        return errorCode;\n    }\n\n    public String getRequestMethod() {\n        return requestMethod;\n    }\n\n    public String getRequestUrl() {\n        return httpUrl.toString();\n    }\n\n    public HttpUrl getHttpUrl() {\n        return httpUrl;\n    }\n\n    public Headers getResponseHeaders() {\n        return responseHeaders;\n    }\n\n    @Nullable\n    @Override\n    public String getLocalizedMessage() {\n        return errorCode;\n    }\n\n    @Override\n    public String toString() {\n        String result = getClass().getName() + \": code is \" + errorCode;\n        String message = getMessage();\n        if (message != null && !message.trim().isEmpty()) {\n            result += \", \" + message;\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/exception/ProxyException.java",
    "content": "package rxhttp.wrapper.exception;\n\n\nimport java.io.IOException;\n\nimport okhttp3.Request;\n\n/**\n * The purpose of this class is to print exceptions with extension information\n * User: ljx\n * Date: 2023/11/10\n * Time: 21:29\n */\npublic class ProxyException extends IOException {\n\n    private final Throwable proxyCause;\n\n    public ProxyException(Request request, Throwable throwable) {\n        this(request.url().toString(), throwable);\n    }\n\n    public ProxyException(String message, Throwable throwable) {\n        super(message);\n        this.proxyCause = throwable;\n        setStackTrace(throwable.getStackTrace());\n    }\n\n    @Override\n    public String toString() {\n        return proxyCause.toString() + \", \" + getMessage();\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/intercept/CacheInterceptor.kt",
    "content": "package rxhttp.wrapper.intercept\n\nimport okhttp3.Interceptor\nimport okhttp3.Request\nimport okhttp3.Response\nimport rxhttp.RxHttpPlugins\nimport rxhttp.wrapper.OkHttpCompat\nimport rxhttp.wrapper.cache.CacheMode\nimport rxhttp.wrapper.cache.CacheStrategy\nimport rxhttp.wrapper.cache.InternalCache\nimport rxhttp.wrapper.exception.CacheReadFailedException\nimport java.io.IOException\n\n/**\n * User: ljx\n * Date: 2020/9/3\n * Time: 17:58\n */\nclass CacheInterceptor(\n    private val cacheStrategy: CacheStrategy\n) : Interceptor {\n\n    private val cache: InternalCache by lazy { RxHttpPlugins.getCacheOrThrow() }  //缓存读取\n\n    override fun intercept(chain: Interceptor.Chain): Response {\n        val request = chain.request()\n        val cacheResponse = beforeReadCache(request)\n        if (cacheResponse != null) return cacheResponse  //缓存有效，直接返回\n        try {\n            //发起请求\n            val response = chain.proceed(request)\n            return if (!cacheModeIs(CacheMode.ONLY_NETWORK)) {\n                //非ONLY_NETWORK模式下,请求成功，写入缓存\n                cache.put(response, cacheStrategy.cacheKey)\n            } else {\n                response\n            }\n        } catch (e: Throwable) {\n            var networkResponse: Response? = null\n            if (cacheModeIs(CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE)) {\n                //请求失败，读取缓存\n                networkResponse = getCacheResponse(request, cacheStrategy.cacheValidTime)\n            }\n            return networkResponse ?: throw e\n        }\n    }\n\n    private fun beforeReadCache(request: Request): Response? {\n        return if (cacheModeIs(CacheMode.ONLY_CACHE, CacheMode.READ_CACHE_FAILED_REQUEST_NETWORK)) {\n            //读取缓存\n            val cacheResponse = getCacheResponse(request, cacheStrategy.cacheValidTime)\n            if (cacheResponse == null) {\n                if (cacheModeIs(CacheMode.ONLY_CACHE)) throw CacheReadFailedException(\"Cache read failed\")\n                return null\n            }\n            cacheResponse\n        } else null\n    }\n\n    private fun cacheModeIs(vararg cacheModes: CacheMode): Boolean {\n        val cacheMode = cacheStrategy.cacheMode\n        return cacheModes.any { it == cacheMode }\n    }\n\n    @Throws(IOException::class)\n    private fun getCacheResponse(request: Request, validTime: Long): Response? {\n        val cacheResponse = cache[request, cacheStrategy.cacheKey]\n        return if (cacheResponse != null) {\n            // Verify cache validity\n            val receivedTime = OkHttpCompat.receivedResponseAtMillis(cacheResponse)\n            if (validTime == Long.MAX_VALUE || System.currentTimeMillis() - receivedTime <= validTime)\n                cacheResponse\n            else\n                null //Cache expired, return null\n        } else null\n    }\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/intercept/LogInterceptor.kt",
    "content": "package rxhttp.wrapper.intercept\n\nimport okhttp3.Interceptor\nimport okhttp3.OkHttpClient\nimport okhttp3.Response\nimport rxhttp.wrapper.OkHttpCompat\nimport rxhttp.wrapper.utils.LogTime\nimport rxhttp.wrapper.utils.LogUtil\n\n\n/**\n * Print the request start/end log\n *\n * User: ljx\n * Date: 2021/10/17\n * Time: 16:42\n */\nclass LogInterceptor(private val okClient: OkHttpClient) : Interceptor {\n\n    override fun intercept(chain: Interceptor.Chain): Response {\n        val request = chain.request()\n        // Prints the request start log\n        LogUtil.log(request, OkHttpCompat.cookieJar(okClient))\n        val logTime = LogTime()\n        val response = chain.proceed(request)\n        // Prints the request end log\n        LogUtil.log(response, logTime)\n        return response\n    }\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/intercept/RangeInterceptor.java",
    "content": "package rxhttp.wrapper.intercept;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\n\nimport okhttp3.Interceptor;\nimport okhttp3.Request;\nimport okhttp3.Response;\nimport rxhttp.wrapper.callback.OutputStreamFactory;\nimport rxhttp.wrapper.entity.DownloadOffSize;\n\n/**\n * User: ljx\n * Date: 2022/10/18\n * Time: 21:41\n */\npublic class RangeInterceptor implements Interceptor {\n    @NotNull\n    @Override\n    public Response intercept(@NotNull Chain chain) throws IOException {\n        Request request = chain.request();\n        OutputStreamFactory<?> osFactory = request.tag(OutputStreamFactory.class);\n        long startIndex;\n        if (osFactory != null && (startIndex = osFactory.offsetSize()) >= 0) {\n            String rangeHeader = \"bytes=\" + startIndex + \"-\";\n            request = request.newBuilder()\n                .addHeader(\"Range\", rangeHeader)\n                .tag(DownloadOffSize.class, new DownloadOffSize(startIndex))\n                .build();\n        }\n        return chain.proceed(request);\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/AbstractBodyParam.java",
    "content": "package rxhttp.wrapper.param;\n\nimport okhttp3.RequestBody;\nimport rxhttp.wrapper.callback.ProgressCallback;\nimport rxhttp.wrapper.callback.ProgressCallbackHelper;\nimport rxhttp.wrapper.progress.ProgressRequestBody;\n\n/**\n * User: ljx\n * Date: 2020-09-07\n * Time: 15:08\n */\npublic abstract class AbstractBodyParam<P extends AbstractBodyParam<P>> extends AbstractParam<P> {\n\n    //Upload progress callback\n    private ProgressCallbackHelper callback;\n\n    /**\n     * @param url    request url\n     * @param method {@link Method#POST}、{@link Method#PUT}、{@link Method#DELETE}、{@link Method#PATCH}\n     */\n    public AbstractBodyParam(String url, Method method) {\n        super(url, method);\n    }\n\n    @Override\n    public final RequestBody buildRequestBody() {\n        RequestBody requestBody = getRequestBody();\n        //Wrap RequestBody if callback not null\n        return callback != null ? new ProgressRequestBody(requestBody, callback) : requestBody;\n    }\n\n    public final P setProgressCallback(int minPeriod, ProgressCallback callback) {\n        this.callback = new ProgressCallbackHelper(minPeriod, callback);\n        return self();\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/AbstractParam.java",
    "content": "package rxhttp.wrapper.param;\n\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Objects;\n\nimport okhttp3.CacheControl;\nimport okhttp3.Headers;\nimport okhttp3.Headers.Builder;\nimport okhttp3.HttpUrl;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.wrapper.cache.CacheMode;\nimport rxhttp.wrapper.cache.CacheStrategy;\nimport rxhttp.wrapper.callback.IConverter;\nimport rxhttp.wrapper.entity.KeyValuePair;\nimport rxhttp.wrapper.utils.BuildUtil;\nimport rxhttp.wrapper.utils.CacheUtil;\n\n/**\n * User: ljx\n * Date: 2019/1/19\n * Time: 14:35\n */\npublic abstract class AbstractParam<P extends Param<P>> extends Param<P> {\n\n    private String url;    //链接地址\n    private Builder HBuilder; //请求头构造器\n    private final Method method;  //请求方法\n    private final CacheStrategy cacheStrategy;  //缓存策略\n    private List<KeyValuePair> queryParam; //查询参数，拼接在Url后面\n    private List<KeyValuePair> paths;      //Replace '{XXX}' in the url\n    private final Request.Builder requestBuilder = new Request.Builder(); //请求构造器\n\n    private boolean isAssemblyEnabled = true;//是否添加公共参数\n\n    /**\n     * @param url    请求路径\n     * @param method Method#GET  Method#HEAD  Method#POST  Method#PUT  Method#DELETE  Method#PATCH\n     */\n    public AbstractParam(@NotNull String url, Method method) {\n        this.url = url;\n        this.method = method;\n        cacheStrategy = RxHttpPlugins.getCacheStrategy();\n    }\n\n    public P setUrl(@NotNull String url) {\n        this.url = url;\n        return self();\n    }\n\n    @Override\n    public P addPath(String name, Object value) {\n        return addPath(new KeyValuePair(name, value));\n    }\n\n    @Override\n    public P addEncodedPath(String name, Object value) {\n        return addPath(new KeyValuePair(name, value, true));\n    }\n\n    private P addPath(KeyValuePair keyValuePair) {\n        if (paths == null) paths = new ArrayList<>();\n        paths.add(keyValuePair);\n        return self();\n    }\n\n    @Override\n    public P addQuery(String key, @Nullable Object value) {\n        return addQuery(new KeyValuePair(key, value));\n    }\n\n    @Override\n    public P addEncodedQuery(String key, @Nullable Object value) {\n        return addQuery(new KeyValuePair(key, value, true));\n    }\n\n    private P addQuery(KeyValuePair keyValuePair) {\n        if (queryParam == null) queryParam = new ArrayList<>();\n        queryParam.add(keyValuePair);\n        return self();\n    }\n\n    @Nullable\n    public List<KeyValuePair> getQueryParam() {\n        return queryParam;\n    }\n\n    public List<KeyValuePair> getPaths() {\n        return paths;\n    }\n\n    @Override\n    public final String getUrl() {\n        return getHttpUrl().toString();\n    }\n\n    @Override\n    public final String getSimpleUrl() {\n        return url;\n    }\n\n    @Override\n    public HttpUrl getHttpUrl() {\n        return BuildUtil.getHttpUrl(url, queryParam, paths);\n    }\n\n    @Override\n    public Method getMethod() {\n        return method;\n    }\n\n    @Nullable\n    @Override\n    public final Headers getHeaders() {\n        return HBuilder == null ? null : HBuilder.build();\n    }\n\n    @Override\n    public final Builder getHeadersBuilder() {\n        if (HBuilder == null)\n            HBuilder = new Builder();\n        return HBuilder;\n    }\n\n    @Override\n    public P setHeadersBuilder(Builder builder) {\n        HBuilder = builder;\n        return self();\n    }\n\n    @Override\n    public P cacheControl(CacheControl cacheControl) {\n        requestBuilder.cacheControl(cacheControl);\n        return self();\n    }\n\n    @Override\n    public <T> P tag(Class<? super T> type, T tag) {\n        requestBuilder.tag(type, tag);\n        return self();\n    }\n\n    @Override\n    public final P setAssemblyEnabled(boolean enabled) {\n        isAssemblyEnabled = enabled;\n        return self();\n    }\n\n    @Override\n    public final boolean isAssemblyEnabled() {\n        return isAssemblyEnabled;\n    }\n\n    public Request.Builder getRequestBuilder() {\n        return requestBuilder;\n    }\n\n    @Override\n    public final CacheStrategy getCacheStrategy() {\n        if (getCacheKey() == null) {\n            setCacheKey(buildCacheKey());\n        }\n        return cacheStrategy;\n    }\n\n    @Override\n    public final String getCacheKey() {\n        return cacheStrategy.getCacheKey();\n    }\n\n    @Override\n    public final P setCacheKey(String cacheKey) {\n        cacheStrategy.setCacheKey(cacheKey);\n        return self();\n    }\n\n    public String buildCacheKey() {\n        List<KeyValuePair> queryPairs = CacheUtil.excludeCacheKey(getQueryParam());\n        return BuildUtil.getHttpUrl(getSimpleUrl(), queryPairs, paths).toString();\n    }\n\n    @Override\n    public final long getCacheValidTime() {\n        return cacheStrategy.getCacheValidTime();\n    }\n\n    @Override\n    public final P setCacheValidTime(long cacheTime) {\n        cacheStrategy.setCacheValidTime(cacheTime);\n        return self();\n    }\n\n    @Override\n    public final CacheMode getCacheMode() {\n        return cacheStrategy.getCacheMode();\n    }\n\n    @Override\n    public final P setCacheMode(CacheMode cacheMode) {\n        cacheStrategy.setCacheMode(cacheMode);\n        return self();\n    }\n\n    @Override\n    public final Request buildRequest() {\n        RxHttpPlugins.onParamAssembly(this);\n        return BuildUtil.buildRequest(this, requestBuilder);\n    }\n\n    protected IConverter getConverter() {\n        Request request = getRequestBuilder().build();\n        IConverter converter = request.tag(IConverter.class);\n        return Objects.requireNonNull(converter, \"converter can not be null\");\n    }\n\n    protected final RequestBody convert(Object object) {\n        try {\n            return getConverter().convert(object);\n        } catch (IOException e) {\n            throw new IllegalArgumentException(\"Unable to convert \" + object + \" to RequestBody\", e);\n        }\n    }\n\n    @Override\n    public P removeAllQuery(String key) {\n        remove(queryParam, key);\n        return self();\n    }\n\n    void remove(List<KeyValuePair> list, String key) {\n        if (list == null) return;\n        Iterator<KeyValuePair> iterator = list.iterator();\n        while (iterator.hasNext()) {\n            KeyValuePair next = iterator.next();\n            if (next.getKey().equals(key))\n                iterator.remove();\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    protected P self() {\n        return (P) this;\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/BodyParam.kt",
    "content": "package rxhttp.wrapper.param\n\nimport okhttp3.RequestBody\n\n/**\n * User: ljx\n * Date: 2019-09-11\n * Time: 11:52\n *\n * @param url    request url\n * @param method [Method.POST]、[Method.PUT]、[Method.DELETE]、[Method.PATCH]\n */\nclass BodyParam(\n    url: String,\n    method: Method,\n) : AbstractBodyParam<BodyParam>(url, method) {\n\n    //Content-Type: application/json; charset=utf-8\n    private var body: Any? = null\n    //The Content-Type depends on the RequestBody\n    private var requestBody: RequestBody? = null\n\n    fun setBody(value: Any): BodyParam {\n        body = value\n        requestBody = null\n        return this\n    }\n\n    fun setBody(requestBody: RequestBody): BodyParam {\n        this.requestBody = requestBody\n        body = null\n        return this\n    }\n\n    override fun getRequestBody(): RequestBody {\n        if (body != null) requestBody = convert(body)\n        return requestBody ?: throw NullPointerException(\"requestBody cannot be null, please call the setBody series methods\")\n    }\n\n    override fun add(key: String, value: Any) = this\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/FormParam.java",
    "content": "package rxhttp.wrapper.param;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\nimport okhttp3.MediaType;\nimport okhttp3.MultipartBody;\nimport okhttp3.MultipartBody.Part;\nimport okhttp3.RequestBody;\nimport rxhttp.wrapper.entity.KeyValuePair;\nimport rxhttp.wrapper.utils.BuildUtil;\nimport rxhttp.wrapper.utils.CacheUtil;\n\n/**\n * post/put/patch/delete request\n *\n * Content-Type: application/x-www-form-urlencoded\n *\n * if have file, Content-Type: multipart/form-data\n *\n * call {@link FormParam#setMultiType(MediaType)}, specify Content-Type\n * User: ljx\n * Date: 2019-09-09\n * Time: 21:08\n */\npublic class FormParam extends AbstractBodyParam<FormParam> implements IPart<FormParam> {\n\n    private MediaType multiType;\n\n    private List<Part> partList;  //Part List\n    private List<KeyValuePair> bodyParam; //Param list\n\n    /**\n     * @param url    request url\n     * @param method {@link Method#POST}、{@link Method#PUT}、{@link Method#DELETE}、{@link Method#PATCH}\n     */\n    public FormParam(String url, Method method) {\n        super(url, method);\n    }\n\n    @Override\n    public FormParam add(String key, @Nullable Object value) {\n        return value == null ? this : add(new KeyValuePair(key, value));\n    }\n\n    public FormParam addEncoded(String key, @Nullable Object value) {\n        return value == null ? this : add(new KeyValuePair(key, value, true));\n    }\n\n    public FormParam addAllEncoded(Map<String, ?> map) {\n        for (Entry<String, ?> entry : map.entrySet()) {\n            addEncoded(entry.getKey(), entry.getValue());\n        }\n        return this;\n    }\n\n    public FormParam removeAllBody(String key) {\n        remove(bodyParam, key);\n        return this;\n    }\n\n    public FormParam removeAllBody() {\n        final List<KeyValuePair> bodyParam = this.bodyParam;\n        if (bodyParam != null)\n            bodyParam.clear();\n        return this;\n    }\n\n    public FormParam set(String key, @Nullable Object value) {\n        removeAllBody(key);\n        return add(key, value);\n    }\n\n    public FormParam setEncoded(String key, @Nullable Object value) {\n        removeAllBody(key);\n        return addEncoded(key, value);\n    }\n\n    private FormParam add(KeyValuePair keyValuePair) {\n        List<KeyValuePair> bodyParam = this.bodyParam;\n        if (bodyParam == null) {\n            bodyParam = this.bodyParam = new ArrayList<>();\n        }\n        bodyParam.add(keyValuePair);\n        return this;\n    }\n\n    @Override\n    public FormParam addPart(Part part) {\n        if (partList == null) {\n            partList = new ArrayList<>();\n            if (!isMultipart()) setMultiType(MultipartBody.FORM);\n        }\n        partList.add(part);\n        return this;\n    }\n\n    //Set the MIME type\n    public FormParam setMultiType(MediaType multiType) {\n        this.multiType = multiType;\n        return this;\n    }\n\n    public boolean isMultipart() {\n        return multiType != null;\n    }\n\n    @Nullable\n    public MediaType getMultiType() {\n        return multiType;\n    }\n\n    @Override\n    public RequestBody getRequestBody() {\n        return isMultipart() ? BuildUtil.buildMultipartBody(multiType, bodyParam, partList)\n            : BuildUtil.buildFormBody(bodyParam);\n    }\n\n    public List<Part> getPartList() {\n        return partList;\n    }\n\n    public List<KeyValuePair> getBodyParam() {\n        return bodyParam;\n    }\n\n    @Override\n    public String buildCacheKey() {\n        List<KeyValuePair> cachePairs = new ArrayList<>();\n        List<KeyValuePair> queryPairs = getQueryParam();\n        List<KeyValuePair> bodyPairs = bodyParam;\n        if (queryPairs != null)\n            cachePairs.addAll(queryPairs);\n        if (bodyPairs != null)\n            cachePairs.addAll(bodyPairs);\n        List<KeyValuePair> pairs = CacheUtil.excludeCacheKey(cachePairs);\n        return BuildUtil.getHttpUrl(getSimpleUrl(), pairs, getPaths()).toString();\n    }\n\n    @Override\n    public String toString() {\n        String url = getSimpleUrl();\n        if (url.startsWith(\"http\")) {\n            url = getUrl();\n        }\n        return \"FormParam{\" +\n            \"url = \" + url +\n            \" bodyParam = \" + bodyParam +\n            '}';\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/ICache.java",
    "content": "package rxhttp.wrapper.param;\n\nimport rxhttp.wrapper.cache.CacheMode;\nimport rxhttp.wrapper.cache.CacheStrategy;\n\n/**\n * User: ljx\n * Date: 2019-12-15\n * Time: 14:08\n */\npublic interface ICache<P extends Param<P>> {\n\n    P setCacheKey(String cacheKey);\n\n    P setCacheValidTime(long cacheTime);\n\n    P setCacheMode(CacheMode cacheMode);\n\n    CacheStrategy getCacheStrategy();\n\n    String getCacheKey();\n\n    long getCacheValidTime();\n\n    CacheMode getCacheMode();\n\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/IHeaders.java",
    "content": "package rxhttp.wrapper.param;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Map;\nimport java.util.Map.Entry;\n\nimport okhttp3.Headers;\nimport okhttp3.Headers.Builder;\n\n/**\n * User: ljx\n * Date: 2019/1/22\n * Time: 13:58\n */\n@SuppressWarnings(\"unchecked\")\npublic interface IHeaders<P extends Param<P>> {\n\n    Headers getHeaders();\n\n    Headers.Builder getHeadersBuilder();\n\n    P setHeadersBuilder(Headers.Builder builder);\n\n    default String getHeader(String key) {\n        return getHeadersBuilder().get(key);\n    }\n\n    default P addHeader(String key, String value) {\n        getHeadersBuilder().add(key, value);\n        return (P) this;\n    }\n\n    default P addNonAsciiHeader(String key, String value) {\n        getHeadersBuilder().addUnsafeNonAscii(key, value);\n        return (P) this;\n    }\n\n    default P setNonAsciiHeader(String key, String value) {\n        Builder builder = getHeadersBuilder();\n        builder.removeAll(key);\n        builder.addUnsafeNonAscii(key, value);\n        return (P) this;\n    }\n\n    default P addHeader(String line) {\n        getHeadersBuilder().add(line);\n        return (P) this;\n    }\n\n    default P addAllHeader(@NotNull Map<String, String> headers) {\n        for (Entry<String, String> entry : headers.entrySet()) {\n            addHeader(entry.getKey(), entry.getValue());\n        }\n        return (P) this;\n    }\n\n    default P addAllHeader(Headers headers) {\n        getHeadersBuilder().addAll(headers);\n        return (P) this;\n    }\n\n    default P setHeader(String key, String value) {\n        getHeadersBuilder().set(key, value);\n        return (P) this;\n    }\n\n    default P setAllHeader(@NotNull Map<String, String> headers) {\n        for (Entry<String, String> entry : headers.entrySet()) {\n            setHeader(entry.getKey(), entry.getValue());\n        }\n        return (P) this;\n    }\n\n    default P removeAllHeader(String key) {\n        getHeadersBuilder().removeAll(key);\n        return (P) this;\n    }\n\n    /**\n     * 设置断点下载开始位置，结束位置默认为文件末尾\n     *\n     * @param startIndex 开始位置\n     * @return Param\n     */\n    default P setRangeHeader(long startIndex) {\n        return setRangeHeader(startIndex, -1);\n    }\n\n    /**\n     * 设置断点下载范围\n     * 注：\n     * 1、开始位置小于0，及代表下载完整文件\n     * 2、结束位置要大于开始位置，否则结束位置默认为文件末尾\n     *\n     * @param startIndex 开始位置\n     * @param endIndex   结束位置\n     * @return Param\n     */\n    default P setRangeHeader(long startIndex, long endIndex) {\n        if (endIndex < startIndex) endIndex = -1;\n        String headerValue = \"bytes=\" + startIndex + \"-\";\n        if (endIndex >= 0) {\n            headerValue = headerValue + endIndex;\n        }\n        return addHeader(\"Range\", headerValue);\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/IParam.java",
    "content": "package rxhttp.wrapper.param;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\nimport okhttp3.CacheControl;\n\n/**\n * User: ljx\n * Date: 2019/1/19\n * Time: 10:25\n */\n@SuppressWarnings(\"unchecked\")\npublic interface IParam<P extends Param<P>> {\n\n    P setUrl(@NotNull String url);\n\n    P add(String key, Object value);\n\n    P addPath(String name, Object value);\n\n    P addEncodedPath(String name, Object value);\n\n    default P addAll(@NotNull Map<String, ?> map) {\n        for (Entry<String, ?> entry : map.entrySet()) {\n            add(entry.getKey(), entry.getValue());\n        }\n        return (P) this;\n    }\n\n    P addQuery(String key, @Nullable Object value);\n\n    P addEncodedQuery(String key, @Nullable Object value);\n\n    P removeAllQuery(String key);\n\n    default P setQuery(String key, @Nullable Object value) {\n        removeAllQuery(key);\n        return addQuery(key, value);\n    }\n\n    default P setEncodedQuery(String key, @Nullable Object value) {\n        removeAllQuery(key);\n        return addEncodedQuery(key, value);\n    }\n\n    default P addAllQuery(String key, @Nullable List<?> values) {\n        if (values == null) return addQuery(key, null);\n        for (Object value : values) {\n            addQuery(key, value);\n        }\n        return (P) this;\n    }\n\n    default P addAllEncodedQuery(String key, @Nullable List<?> values) {\n        if (values == null) return addEncodedQuery(key, null);\n        for (Object value : values) {\n            addEncodedQuery(key, value);\n        }\n        return (P) this;\n    }\n\n    default P addAllQuery(@NotNull Map<String, ?> map) {\n        for (Entry<String, ?> entry : map.entrySet()) {\n            addQuery(entry.getKey(), entry.getValue());\n        }\n        return (P) this;\n    }\n\n    default P addAllEncodedQuery(@NotNull Map<String, ?> map) {\n        for (Entry<String, ?> entry : map.entrySet()) {\n            addEncodedQuery(entry.getKey(), entry.getValue());\n        }\n        return (P) this;\n    }\n\n    /**\n     * @return 判断是否对参数添加装饰，即是否添加公共参数\n     */\n    boolean isAssemblyEnabled();\n\n    /**\n     * 设置是否对参数添加装饰，即是否添加公共参数\n     *\n     * @param enabled true 是\n     * @return Param\n     */\n    P setAssemblyEnabled(boolean enabled);\n\n    default P tag(@Nullable Object tag) {\n        return tag(Object.class, tag);\n    }\n\n    <T> P tag(Class<? super T> type, @Nullable T tag);\n\n    P cacheControl(CacheControl cacheControl);\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/IPart.java",
    "content": "package rxhttp.wrapper.param;\n\nimport okhttp3.MultipartBody.Part;\n\n/**\n * User: ljx\n * Date: 2019-05-19\n * Time: 18:18\n */\npublic interface IPart<P extends Param<P>> {\n\n    P addPart(Part part);\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/IRequest.java",
    "content": "package rxhttp.wrapper.param;\n\n\nimport okhttp3.Headers;\nimport okhttp3.HttpUrl;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\n\n/**\n * 用于构建一个{@link Request}\n * User: ljx\n * Date: 2019/1/19\n * Time: 17:24\n */\npublic interface IRequest {\n\n    /**\n     * @return 带查询参数参数的url\n     */\n    String getUrl();\n\n    /**\n     * @return 不带参数的url\n     */\n    String getSimpleUrl();\n\n    /**\n     * @return HttpUrl\n     */\n    HttpUrl getHttpUrl();\n\n    /**\n     * @return 请求方法，GET、POST等\n     */\n    Method getMethod();\n\n    /**\n     * @return 请求体\n     * GET、HEAD不能有请求体，\n     * POST、PUT、PATCH、PROPPATCH、REPORT请求必须要有请求体\n     * 其它请求可有可无\n     */\n    default RequestBody buildRequestBody() {\n        return getRequestBody();\n    }\n\n    RequestBody getRequestBody();\n\n    /**\n     * @return 请求头信息\n     */\n    Headers getHeaders();\n\n    /**\n     * @return 根据以上定义的方法构建一个请求\n     */\n    Request buildRequest();\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/JsonArrayParam.java",
    "content": "package rxhttp.wrapper.param;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParser;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport okhttp3.HttpUrl;\nimport okhttp3.HttpUrl.Builder;\nimport okhttp3.RequestBody;\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.wrapper.callback.IConverter;\nimport rxhttp.wrapper.callback.JsonConverter;\nimport rxhttp.wrapper.entity.KeyValuePair;\nimport rxhttp.wrapper.utils.BuildUtil;\nimport rxhttp.wrapper.utils.CacheUtil;\nimport rxhttp.wrapper.utils.GsonUtil;\nimport rxhttp.wrapper.utils.JsonUtil;\n\n/**\n * post/put/patch/delete request\n * Content-Type: application/json; charset=utf-8\n * User: ljx\n * Date: 2019-09-09\n * Time: 21:08\n */\npublic class JsonArrayParam extends AbstractBodyParam<JsonArrayParam> {\n\n    private List<Object> bodyParam;\n\n    /**\n     * @param url    request url\n     * @param method {@link Method#POST}、{@link Method#PUT}、{@link Method#DELETE}、{@link Method#PATCH}\n     */\n    public JsonArrayParam(String url, Method method) {\n        super(url, method);\n    }\n\n    @Override\n    public RequestBody getRequestBody() {\n        final List<?> jsonArray = bodyParam;\n        if (jsonArray == null)\n            return RequestBody.create(null, new byte[0]);\n        return convert(jsonArray);\n    }\n\n    /**\n     * JsonArray类型请求，所有add系列方法内部最终都会调用此方法\n     *\n     * @param object Object\n     * @return JsonArrayParam\n     */\n    public JsonArrayParam add(@Nullable Object object) {\n        initList();\n        bodyParam.add(object);\n        return this;\n    }\n\n    @Override\n    public JsonArrayParam add(String key, @Nullable Object value) {\n        HashMap<String, Object> map = new HashMap<>();\n        map.put(key, value);\n        return add(map);\n    }\n\n    public JsonArrayParam addAll(String jsonElement) {\n        JsonElement element = JsonParser.parseString(jsonElement);\n        if (element.isJsonArray()) {\n            return addAll(element.getAsJsonArray());\n        } else if (element.isJsonObject()) {\n            return addAll(element.getAsJsonObject());\n        }\n        return add(JsonUtil.toAny(element));\n    }\n\n    public JsonArrayParam addAll(JsonObject jsonObject) {\n        return addAll(JsonUtil.toMap(jsonObject));\n    }\n\n    @Override\n    public JsonArrayParam addAll(@NotNull Map<String, ?> map) {\n        initList();\n        return super.addAll(map);\n    }\n\n    public JsonArrayParam addAll(JsonArray jsonArray) {\n        return addAll(JsonUtil.toList(jsonArray));\n    }\n\n    public JsonArrayParam addAll(List<?> list) {\n        initList();\n        for (Object object : list) {\n            add(object);\n        }\n        return this;\n    }\n\n    public JsonArrayParam addJsonElement(String jsonElement) {\n        JsonElement element = JsonParser.parseString(jsonElement);\n        return add(JsonUtil.toAny(element));\n    }\n\n    public JsonArrayParam addJsonElement(String key, String jsonElement) {\n        JsonElement element = JsonParser.parseString(jsonElement);\n        return add(key, JsonUtil.toAny(element));\n    }\n\n    @Nullable\n    public List<Object> getBodyParam() {\n        return bodyParam;\n    }\n\n    @Override\n    public String buildCacheKey() {\n        List<KeyValuePair> queryPairs = CacheUtil.excludeCacheKey(getQueryParam());\n        HttpUrl httpUrl = BuildUtil.getHttpUrl(getSimpleUrl(), queryPairs, getPaths());\n        List<Object> list = CacheUtil.excludeCacheKey(bodyParam);\n        String json = GsonUtil.toJson(list);\n        Builder builder = httpUrl.newBuilder().addQueryParameter(\"json\", json);\n        return builder.toString();\n    }\n\n    private void initList() {\n        if (bodyParam == null) bodyParam = new ArrayList<>();\n    }\n\n    @Override\n    protected IConverter getConverter() {\n        IConverter converter = super.getConverter();\n        if (!(converter instanceof JsonConverter)) {\n            converter = RxHttpPlugins.getConverter();\n        }\n        return converter;\n    }\n\n    @Override\n    public String toString() {\n        String url = getSimpleUrl();\n        if (url.startsWith(\"http\")) {\n            url = getUrl();\n        }\n        return \"JsonArrayParam{\" +\n            \"url = \" + url +\n            \" bodyParam = \" + bodyParam +\n            '}';\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/JsonParam.java",
    "content": "package rxhttp.wrapper.param;\n\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParser;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport okhttp3.HttpUrl;\nimport okhttp3.HttpUrl.Builder;\nimport okhttp3.RequestBody;\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.wrapper.callback.IConverter;\nimport rxhttp.wrapper.callback.JsonConverter;\nimport rxhttp.wrapper.entity.KeyValuePair;\nimport rxhttp.wrapper.utils.BuildUtil;\nimport rxhttp.wrapper.utils.CacheUtil;\nimport rxhttp.wrapper.utils.GsonUtil;\nimport rxhttp.wrapper.utils.JsonUtil;\n\n/**\n * post/put/patch/delete request\n * Content-Type: application/json; charset=utf-8\n *\n * User: ljx\n * Date: 2019-09-09\n * Time: 21:08\n */\npublic class JsonParam extends AbstractBodyParam<JsonParam> {\n\n    private Map<String, Object> bodyParam; //请求参数\n\n    /**\n     * @param url    request url\n     * @param method {@link Method#POST}、{@link Method#PUT}、{@link Method#DELETE}、{@link Method#PATCH}\n     */\n    public JsonParam(String url, Method method) {\n        super(url, method);\n    }\n\n    @Override\n    public RequestBody getRequestBody() {\n        final Map<String, Object> bodyParam = this.bodyParam;\n        if (bodyParam == null)\n            return RequestBody.create(null, new byte[0]);\n        return convert(bodyParam);\n    }\n\n    @Override\n    public JsonParam add(String key, @Nullable Object value) {\n        initMap();\n        bodyParam.put(key, value);\n        return this;\n    }\n\n    public JsonParam addAll(String jsonObject) {\n        return addAll(JsonParser.parseString(jsonObject).getAsJsonObject());\n    }\n\n    public JsonParam addAll(JsonObject jsonObject) {\n        return addAll(JsonUtil.toMap(jsonObject));\n    }\n\n    @Override\n    public JsonParam addAll(@NotNull Map<String, ?> map) {\n        initMap();\n        return super.addAll(map);\n    }\n\n    public JsonParam addJsonElement(String key, String jsonElement) {\n        JsonElement element = JsonParser.parseString(jsonElement);\n        return add(key, JsonUtil.toAny(element));\n    }\n\n    public Map<String, Object> getBodyParam() {\n        return bodyParam;\n    }\n\n    @Override\n    public String buildCacheKey() {\n        List<KeyValuePair> queryPairs = CacheUtil.excludeCacheKey(getQueryParam());\n        HttpUrl httpUrl = BuildUtil.getHttpUrl(getSimpleUrl(), queryPairs, getPaths());\n        Map<?, ?> param = CacheUtil.excludeCacheKey(bodyParam);\n        String json = GsonUtil.toJson(param);\n        Builder builder = httpUrl.newBuilder().addQueryParameter(\"json\", json);\n        return builder.toString();\n    }\n\n    private void initMap() {\n        if (bodyParam == null) bodyParam = new LinkedHashMap<>();\n    }\n\n    @Override\n    protected IConverter getConverter() {\n        IConverter converter = super.getConverter();\n        if (!(converter instanceof JsonConverter)) {\n            converter = RxHttpPlugins.getConverter();\n        }\n        return converter;\n    }\n\n    @Override\n    public String toString() {\n        String url = getSimpleUrl();\n        if (url.startsWith(\"http\")) {\n            url = getUrl();\n        }\n        return \"JsonParam{\" +\n            \"url = \" + url +\n            \" bodyParam = \" + bodyParam +\n            '}';\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/Method.java",
    "content": "package rxhttp.wrapper.param;\n\n/**\n * 请求方法\n * User: ljx\n * Date: 2019-09-10\n * Time: 23:18\n */\npublic enum Method {\n\n    GET,\n    HEAD,\n    POST,\n    PUT,\n    PATCH,\n    DELETE,\n    ;\n\n    public boolean isGet() {\n        return name().equals(\"GET\");\n    }\n\n    public boolean isPost() {\n        return name().equals(\"POST\");\n    }\n\n    public boolean isHead() {\n        return name().equals(\"HEAD\");\n    }\n\n    public boolean isPut() {\n        return name().equals(\"PUT\");\n    }\n\n    public boolean isPatch() {\n        return name().equals(\"PATCH\");\n    }\n\n    public boolean isDelete() {\n        return name().equals(\"DELETE\");\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/NoBodyParam.java",
    "content": "package rxhttp.wrapper.param;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.Map;\n\nimport okhttp3.RequestBody;\n\n/**\n * Get、Head没有body的请求调用此类\n * User: ljx\n * Date: 2019-09-09\n * Time: 21:08\n */\npublic class NoBodyParam extends AbstractParam<NoBodyParam> {\n\n    /**\n     * @param url    请求路径\n     * @param method Method#GET  Method#HEAD  Method#DELETE\n     */\n    public NoBodyParam(String url, Method method) {\n        super(url, method);\n    }\n\n    @Override\n    public NoBodyParam add(String key, @Nullable Object value) {\n        return addQuery(key, value);\n    }\n\n    public NoBodyParam addEncoded(String key, @Nullable Object value) {\n        return addEncodedQuery(key, value);\n    }\n\n    public NoBodyParam addAllEncoded(@NotNull Map<String, ?> map) {\n        return addAllEncodedQuery(map);\n    }\n\n    @Override\n    public final RequestBody getRequestBody() {\n        return null;\n    }\n\n    @Override\n    public String toString() {\n        String url = getSimpleUrl();\n        if (url.startsWith(\"http\")) {\n            url = getUrl();\n        }\n        return url;\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/param/Param.java",
    "content": "package rxhttp.wrapper.param;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport okhttp3.MediaType;\n\n/**\n * User: ljx\n * Date: 2019/1/19\n * Time: 10:25\n */\npublic abstract class Param<P extends Param<P>> implements IParam<P>, IHeaders<P>, ICache<P>, IRequest {\n\n    public static String DATA_DECRYPT = \"data-decrypt\";\n\n    public static NoBodyParam get(@NotNull String url) {\n        return new NoBodyParam(url, Method.GET);\n    }\n\n    public static NoBodyParam head(@NotNull String url) {\n        return new NoBodyParam(url, Method.HEAD);\n    }\n\n    //The Content-Type depends on the RequestBody\n    public static BodyParam postBody(@NotNull String url) {\n        return new BodyParam(url, Method.POST);\n    }\n\n    public static BodyParam putBody(@NotNull String url) {\n        return new BodyParam(url, Method.PUT);\n    }\n\n    public static BodyParam patchBody(@NotNull String url) {\n        return new BodyParam(url, Method.PATCH);\n    }\n\n    public static BodyParam deleteBody(@NotNull String url) {\n        return new BodyParam(url, Method.DELETE);\n    }\n\n    /**\n     * Content-Type: application/x-www-form-urlencoded\n     * if have file, Content-Type: multipart/form-data\n     * call {@link FormParam#setMultiType(MediaType)}, specify Content-Type\n     *\n     * @param url url\n     * @return FormParam\n     */\n    public static FormParam postForm(@NotNull String url) {\n        return new FormParam(url, Method.POST);\n    }\n\n    public static FormParam putForm(@NotNull String url) {\n        return new FormParam(url, Method.PUT);\n    }\n\n    public static FormParam patchForm(@NotNull String url) {\n        return new FormParam(url, Method.PATCH);\n    }\n\n    public static FormParam deleteForm(@NotNull String url) {\n        return new FormParam(url, Method.DELETE);\n    }\n\n    //Content-Type: application/json; charset=utf-8\n    public static JsonParam postJson(@NotNull String url) {\n        return new JsonParam(url, Method.POST);\n    }\n\n    public static JsonParam putJson(@NotNull String url) {\n        return new JsonParam(url, Method.PUT);\n    }\n\n    public static JsonParam patchJson(@NotNull String url) {\n        return new JsonParam(url, Method.PATCH);\n    }\n\n    public static JsonParam deleteJson(@NotNull String url) {\n        return new JsonParam(url, Method.DELETE);\n    }\n\n    //Content-Type: application/json; charset=utf-8\n    public static JsonArrayParam postJsonArray(@NotNull String url) {\n        return new JsonArrayParam(url, Method.POST);\n    }\n\n    public static JsonArrayParam putJsonArray(@NotNull String url) {\n        return new JsonArrayParam(url, Method.PUT);\n    }\n\n    public static JsonArrayParam patchJsonArray(@NotNull String url) {\n        return new JsonArrayParam(url, Method.PATCH);\n    }\n\n    public static JsonArrayParam deleteJsonArray(@NotNull String url) {\n        return new JsonArrayParam(url, Method.DELETE);\n    }\n\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/parse/OkResponseParser.java",
    "content": "package rxhttp.wrapper.parse;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\n\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\nimport rxhttp.wrapper.OkHttpCompat;\nimport rxhttp.wrapper.entity.EmptyResponseBody;\nimport rxhttp.wrapper.entity.OkResponse;\n\n/**\n * User: ljx\n * Date: 2022/9/22\n * Time: 21:53\n */\npublic class OkResponseParser<T> implements Parser<OkResponse<T>> {\n\n    public final Parser<T> parser;\n\n    public OkResponseParser(Parser<T> parser) {\n        this.parser = parser;\n    }\n\n    @Override\n    public OkResponse<T> onParse(@NotNull Response response) throws IOException {\n        ResponseBody rawBody = response.body();\n\n        // Remove the body's source (the only stateful object) so we can pass the response along.\n        Response emptyResponse =\n            response\n                .newBuilder()\n                .body(new EmptyResponseBody(rawBody.contentType(), rawBody.contentLength()))\n                .build();\n\n\n        if (!emptyResponse.isSuccessful()) {\n            try {\n                // Buffer the entire body to avoid future I/O.\n                ResponseBody bufferedBody = OkHttpCompat.buffer(rawBody);\n                return OkResponse.error(bufferedBody, emptyResponse);\n            } finally {\n                rawBody.close();\n            }\n        }\n        int code = emptyResponse.code();\n        if (code == 204 || code == 205) {\n            rawBody.close();\n            return OkResponse.success(null, emptyResponse);\n        }\n\n        T body = parser.onParse(response);\n        return OkResponse.success(body, emptyResponse);\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/parse/Parser.java",
    "content": "package rxhttp.wrapper.parse;\n\nimport java.io.IOException;\n\nimport okhttp3.Response;\n\n/**\n *[okhttp3.Response] to T\n * User: ljx\n * Date: 2018/10/23\n * Time: 13:49\n */\npublic interface Parser<T> {\n\n    T onParse(Response response) throws IOException;\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/parse/SmartParser.java",
    "content": "package rxhttp.wrapper.parse;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\n\nimport okhttp3.Response;\nimport rxhttp.wrapper.utils.Converter;\nimport rxhttp.wrapper.utils.TypeUtil;\n\n/**\n * Convert [Response] to [T]\n * User: ljx\n * Date: 2018/10/23\n * Time: 13:49\n */\npublic class SmartParser<T> extends TypeParser<T> {\n\n    protected SmartParser() {\n    }\n\n    public SmartParser(Type type) {\n        super(type);\n    }\n\n    @Override\n    public T onParse(Response response) throws IOException {\n        return Converter.convert(response, types[0]);\n    }\n\n    @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n    public static <T> Parser<T> wrap(Type type) {\n        Type actualType = TypeUtil.getActualType(type);\n        if (actualType == null) actualType = type;\n        SmartParser smartParser = new SmartParser(actualType);\n        return actualType == type ? smartParser : new OkResponseParser(smartParser);\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/parse/StreamParser.kt",
    "content": "package rxhttp.wrapper.parse\n\nimport okhttp3.Response\nimport rxhttp.wrapper.OkHttpCompat\nimport rxhttp.wrapper.callback.OutputStreamFactory\nimport rxhttp.wrapper.callback.ProgressCallback\nimport rxhttp.wrapper.callback.ProgressCallbackHelper\nimport rxhttp.wrapper.entity.DownloadOffSize\nimport rxhttp.wrapper.utils.LogTime\nimport rxhttp.wrapper.utils.LogUtil\nimport rxhttp.wrapper.utils.isPartialContent\nimport rxhttp.wrapper.utils.writeTo\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.OutputStream\n\n/**\n * User: ljx\n * Date: 2020/9/4\n * Time: 21:39\n */\nopen class StreamParser<T>(\n    private val osFactory: OutputStreamFactory<T>,\n) : Parser<T> {\n\n    private var callback: ProgressCallbackHelper? = null\n\n    fun setProgressCallback(minPeriod: Int, progressCallback: ProgressCallback) {\n        callback = ProgressCallbackHelper(minPeriod, progressCallback)\n    }\n\n    override fun onParse(response: Response): T {\n        val body = OkHttpCompat.throwIfFail(response)\n        val expandOs = osFactory.openOutputStream(response)\n        val expand: T = expandOs.expand\n        expandOs.use { os ->\n            LogUtil.log(\"Download start: $expand\")\n            val logTime = LogTime()\n            body.byteStream().use { it.writeTo(os, response, callback) }\n            LogUtil.log(\"Download end, cost ${logTime.tookMs()}ms: $expand\")\n        }\n        return expand\n    }\n\n    @Throws(IOException::class)\n    private fun InputStream.writeTo(\n        os: OutputStream,\n        response: Response,\n        callback: ProgressCallbackHelper?,\n    ) {\n        if (callback == null) {\n            writeTo(os)\n            return\n        }\n\n        val offsetSize = if (response.isPartialContent()) {\n            OkHttpCompat.request(response).tag(DownloadOffSize::class.java)?.offSize ?: 0\n        } else {\n            0\n        }\n        var contentLength = OkHttpCompat.getContentLength(response)\n        if (contentLength != -1L) contentLength += offsetSize\n        if (contentLength == -1L) {\n            LogUtil.log(\"Unable to calculate callback progress without `Content-Length` response header\")\n        }\n\n        callback.onStart(offsetSize)\n        writeTo(os) { callback.onProgress(it.toLong(), contentLength) }\n        if (contentLength == -1L) {\n            //Promise that download end events can be callback if contentLength is -1\n            callback.onProgress(-1, contentLength)\n        }\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/parse/TypeParser.java",
    "content": "package rxhttp.wrapper.parse;\n\nimport java.lang.reflect.Type;\n\nimport rxhttp.wrapper.utils.TypeUtil;\n\n\n/**\n * User: ljx\n * Date: 2022/10/19\n * Time: 20:17\n */\npublic abstract class TypeParser<T> implements Parser<T> {\n\n    protected final Type[] types;\n\n    public TypeParser() {\n        types = TypeUtil.getActualTypeParameters(getClass());\n    }\n\n    protected TypeParser(Type... types) {\n        this.types = types;\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/progress/ProgressRequestBody.java",
    "content": "package rxhttp.wrapper.progress;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\n\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okio.Buffer;\nimport okio.BufferedSink;\nimport okio.ForwardingSink;\nimport okio.Okio;\nimport okio.Sink;\nimport rxhttp.wrapper.callback.ProgressCallbackHelper;\n\n//Track upload progress\npublic class ProgressRequestBody extends RequestBody {\n\n    @NotNull\n    private final RequestBody requestBody;\n    @NotNull\n    private final ProgressCallbackHelper callback;\n\n    public ProgressRequestBody(@NotNull RequestBody requestBody, @NotNull ProgressCallbackHelper callback) {\n        this.requestBody = requestBody;\n        this.callback = callback;\n    }\n\n    @NotNull\n    public RequestBody getRequestBody() {\n        return requestBody;\n    }\n\n    @Override\n    public MediaType contentType() {\n        return requestBody.contentType();\n    }\n\n    @Override\n    public long contentLength() throws IOException {\n        return requestBody.contentLength();\n    }\n\n    @Override\n    public void writeTo(@NotNull BufferedSink sink) throws IOException {\n        if (sink instanceof Buffer\n            || sink.toString().contains(\n            \"com.android.tools.profiler.support.network.HttpTracker$OutputStreamTracker\")) {\n            requestBody.writeTo(sink);\n        } else {\n            BufferedSink bufferedSink = Okio.buffer(sink(sink));\n            requestBody.writeTo(bufferedSink);\n            bufferedSink.close();\n        }\n    }\n\n    private Sink sink(Sink sink) {\n        callback.onStart(0);\n        return new ForwardingSink(sink) {\n            long contentLength = Long.MIN_VALUE;\n\n            @Override\n            public void write(@NotNull Buffer source, long byteCount) throws IOException {\n                super.write(source, byteCount);\n                if (contentLength == Long.MIN_VALUE) {\n                    contentLength = contentLength();\n                }\n                callback.onProgress(byteCount, contentLength);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/ssl/HttpsUtils.java",
    "content": "package rxhttp.wrapper.ssl;\n\n\nimport java.io.IOException;\nimport java.io.InputStream;\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;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509Certificate;\n\nimport javax.net.ssl.KeyManager;\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509TrustManager;\n\n/**\n * 处理https证书问题，此类出处为鸿洋的okhttputils\n * 详细用法请查看：https://github.com/liujingxing/rxhttp/wiki/%E5%85%B3%E4%BA%8EHttps\n * User: ljx\n * Date: 2020/05/01\n * Time: 11:13\n */\npublic class HttpsUtils {\n    public static class SSLParams {\n        public SSLSocketFactory sSLSocketFactory;\n        public X509TrustManager trustManager;\n    }\n\n    public static SSLParams getSslSocketFactory() {\n        return getSslSocketFactory(null, null, null);\n    }\n\n    public static SSLParams getSslSocketFactory(InputStream[] certificates, InputStream bksFile, String password) {\n        SSLParams sslParams = new SSLParams();\n        try {\n            TrustManager[] trustManagers = prepareTrustManager(certificates);\n            KeyManager[] keyManagers = prepareKeyManager(bksFile, password);\n            SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n            X509TrustManager trustManager;\n            if (trustManagers != null) {\n                trustManager = new MyTrustManager(chooseTrustManager(trustManagers));\n            } else {\n                trustManager = new UnSafeTrustManager();\n            }\n            sslContext.init(keyManagers, new TrustManager[]{trustManager}, null);\n            sslParams.sSLSocketFactory = sslContext.getSocketFactory();\n            sslParams.trustManager = trustManager;\n            return sslParams;\n        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {\n            throw new AssertionError(e);\n        }\n    }\n\n    private static class UnSafeTrustManager implements X509TrustManager {\n        @Override\n        public void checkClientTrusted(X509Certificate[] chain, String authType)\n            throws CertificateException {\n        }\n\n        @Override\n        public void checkServerTrusted(X509Certificate[] chain, String authType)\n            throws CertificateException {\n        }\n\n        @Override\n        public X509Certificate[] getAcceptedIssuers() {\n            return new X509Certificate[]{};\n        }\n    }\n\n    private static TrustManager[] prepareTrustManager(InputStream... certificates) {\n        if (certificates == null || certificates.length <= 0) return null;\n        try {\n\n            CertificateFactory certificateFactory = CertificateFactory.getInstance(\"X.509\");\n            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());\n            keyStore.load(null);\n            int index = 0;\n            for (InputStream certificate : certificates) {\n                String certificateAlias = Integer.toString(index++);\n                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));\n                try {\n                    if (certificate != null)\n                        certificate.close();\n                } catch (IOException e) {\n                }\n            }\n            TrustManagerFactory trustManagerFactory = null;\n\n            trustManagerFactory = TrustManagerFactory.\n                getInstance(TrustManagerFactory.getDefaultAlgorithm());\n            trustManagerFactory.init(keyStore);\n\n            return trustManagerFactory.getTrustManagers();\n        } catch (NoSuchAlgorithmException | CertificateException | KeyStoreException e) {\n            e.printStackTrace();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n\n    }\n\n    private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) {\n        try {\n            if (bksFile == null || password == null) return null;\n\n            KeyStore clientKeyStore = KeyStore.getInstance(\"BKS\");\n            clientKeyStore.load(bksFile, password.toCharArray());\n            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());\n            keyManagerFactory.init(clientKeyStore, password.toCharArray());\n            return keyManagerFactory.getKeyManagers();\n\n        } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | CertificateException | IOException e) {\n            e.printStackTrace();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {\n        for (TrustManager trustManager : trustManagers) {\n            if (trustManager instanceof X509TrustManager) {\n                return (X509TrustManager) trustManager;\n            }\n        }\n        return null;\n    }\n\n\n    private static class MyTrustManager implements X509TrustManager {\n        private X509TrustManager defaultTrustManager;\n        private X509TrustManager localTrustManager;\n\n        public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {\n            TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n            var4.init((KeyStore) null);\n            defaultTrustManager = chooseTrustManager(var4.getTrustManagers());\n            this.localTrustManager = localTrustManager;\n        }\n\n\n        @Override\n        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n\n        }\n\n        @Override\n        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n            try {\n                defaultTrustManager.checkServerTrusted(chain, authType);\n            } catch (CertificateException ce) {\n                localTrustManager.checkServerTrusted(chain, authType);\n            }\n        }\n\n\n        @Override\n        public X509Certificate[] getAcceptedIssuers() {\n            return new X509Certificate[0];\n        }\n    }\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/BuildUtil.java",
    "content": "package rxhttp.wrapper.utils;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.net.Uri;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.net.URLConnection;\nimport java.util.List;\nimport java.util.regex.Pattern;\n\nimport okhttp3.FormBody;\nimport okhttp3.Headers;\nimport okhttp3.HttpUrl;\nimport okhttp3.MediaType;\nimport okhttp3.MultipartBody;\nimport okhttp3.MultipartBody.Part;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport rxhttp.wrapper.entity.KeyValuePair;\nimport rxhttp.wrapper.param.IRequest;\n\n/**\n * User: ljx\n * Date: 2017/12/1\n * Time: 18:36\n */\npublic class BuildUtil {\n\n    private static final Pattern PATH_TRAVERSAL = Pattern.compile(\"(.*/)?(\\\\.|%2e|%2E){1,2}(/.*)?\");\n\n    // Build Request\n    public static Request buildRequest(@NotNull IRequest r, @NotNull Request.Builder builder) {\n        builder.url(r.getHttpUrl())\n            .method(r.getMethod().name(), r.buildRequestBody());\n        Headers headers = r.getHeaders();\n        if (headers != null) {\n            builder.headers(headers);\n        }\n        return builder.build();\n    }\n\n    // Build FormBody\n    public static RequestBody buildFormBody(List<KeyValuePair> pairs) {\n        FormBody.Builder builder = new FormBody.Builder();\n        if (pairs != null) {\n            for (KeyValuePair pair : pairs) {\n                Object value = pair.getValue();\n                if (value == null) continue;\n                String name = pair.getKey();\n                if (pair.isEncoded()) {\n                    builder.addEncoded(name, value.toString());\n                } else {\n                    builder.add(name, value.toString());\n                }\n            }\n        }\n        return builder.build();\n    }\n\n    // Build MultipartBody\n    public static RequestBody buildMultipartBody(MediaType multiType, List<KeyValuePair> pairs, List<Part> partList) {\n        MultipartBody.Builder builder = new MultipartBody.Builder();\n        builder.setType(multiType);\n        if (pairs != null) {\n            for (KeyValuePair pair : pairs) {\n                Object value = pair.getValue();\n                if (value == null) continue;\n                String name = pair.getKey();\n                builder.addFormDataPart(name, value.toString());\n            }\n        }\n        if (partList != null) {\n            for (Part part : partList) {\n                builder.addPart(part);\n            }\n        }\n        return builder.build();\n    }\n\n    //Build HttpUrl\n    public static HttpUrl getHttpUrl(@NotNull String url, @Nullable List<KeyValuePair> queryList,\n                                     @Nullable List<KeyValuePair> paths) {\n        if (paths != null) {\n            for (KeyValuePair path : paths) {\n                String name = path.getKey();\n                Object value = path.getValue();\n                if (value == null) {\n                    throw new IllegalArgumentException(\"Path parameter \\\"\" + name + \"\\\" value must not be null.\");\n                }\n                String replacement = PathEncoderKt.canonicalizeForPath(value.toString(), path.isEncoded());\n                String newUrl = url.replace(\"{\" + name + \"}\", replacement);\n                if (PATH_TRAVERSAL.matcher(newUrl).matches()) {\n                    throw new IllegalArgumentException(\n                        \"Path parameters shouldn't perform path traversal ('.' or '..'): \" + name + \" is \" + value);\n                }\n                url = newUrl;\n            }\n        }\n        HttpUrl httpUrl = HttpUrl.get(url);\n        if (queryList == null || queryList.size() == 0) return httpUrl;\n        HttpUrl.Builder builder = httpUrl.newBuilder();\n        for (KeyValuePair pair : queryList) {\n            String name = pair.getKey();\n            Object object = pair.getValue();\n            String value = object == null ? null : object.toString();\n            if (pair.isEncoded()) {\n                builder.addEncodedQueryParameter(name, value);\n            } else {\n                builder.addQueryParameter(name, value);\n            }\n        }\n        return builder.build();\n    }\n\n    //For compatibility with okHTTP 3.x version, only written in Java\n    public static MediaType getMediaType(@Nullable String filename) {\n        if (filename == null) return null;\n        int index = filename.lastIndexOf(\".\") + 1;\n        String fileSuffix = filename.substring(index);\n        String contentType = URLConnection.guessContentTypeFromName(fileSuffix);\n        return contentType != null ? MediaType.parse(contentType) : null;\n    }\n\n    //For compatibility with okHTTP 3.x version, only written in Java\n    public static MediaType getMediaTypeByUri(Context context, Uri uri) {\n        if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {\n            return BuildUtil.getMediaType((uri.getLastPathSegment()));\n        } else {\n            String contentType = context.getContentResolver().getType(uri);\n            return contentType != null ? MediaType.parse(contentType) : null;\n        }\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/CacheUtil.java",
    "content": "package rxhttp.wrapper.utils;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.wrapper.entity.KeyValuePair;\n\n/**\n * User: ljx\n * Date: 2019-12-21\n * Time: 15:41\n */\npublic class CacheUtil {\n\n    //过滤要剔除的cacheKey\n    @SuppressWarnings(\"unchecked\")\n    public static <T> List<T> excludeCacheKey(List<T> objects) {\n        if (objects == null) return null;\n        List<String> excludeCacheKeys = RxHttpPlugins.getExcludeCacheKeys();\n        if (excludeCacheKeys.isEmpty()) return objects;\n        List<Object> newList = new ArrayList<>();\n        for (Object object : objects) {\n            if (object instanceof KeyValuePair) {\n                KeyValuePair pair = (KeyValuePair) object;\n                if (excludeCacheKeys.contains(pair.getKey())) continue;\n            } else if (object instanceof Map) {\n                Map<?, ?> map = excludeCacheKey((Map<?, ?>) object);\n                if (map == null || map.size() == 0) continue;\n            } else if (object instanceof JsonObject) {\n                JsonObject jsonObject = excludeCacheKey((JsonObject) object);\n                if (jsonObject == null || jsonObject.size() == 0) continue;\n            }\n            newList.add(object);\n        }\n        return (List<T>) newList;\n    }\n\n    //过滤要剔除的cacheKey\n    public static Map<?, ?> excludeCacheKey(Map<?, ?> param) {\n        if (param == null) return null;\n        List<String> excludeCacheKeys = RxHttpPlugins.getExcludeCacheKeys();\n        if (excludeCacheKeys.isEmpty()) return param;\n        Map<String, Object> newParam = new LinkedHashMap<>();\n        for (Map.Entry<?, ?> entry : param.entrySet()) {\n            String key = entry.getKey().toString();\n            if (excludeCacheKeys.contains(key)) continue;\n            newParam.put(key, entry.getValue());\n        }\n        return newParam;\n    }\n\n    //过滤要剔除的cacheKey\n    private static JsonObject excludeCacheKey(JsonObject jsonObject) {\n        if (jsonObject == null) return null;\n        List<String> excludeCacheKeys = RxHttpPlugins.getExcludeCacheKeys();\n        if (excludeCacheKeys.isEmpty()) return jsonObject;\n        JsonObject newParam = new JsonObject();\n        for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {\n            String key = entry.getKey();\n            if (excludeCacheKeys.contains(key)) continue;\n            newParam.add(key, entry.getValue());\n        }\n        return newParam;\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/Converter.java",
    "content": "package rxhttp.wrapper.utils;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\n\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\nimport rxhttp.Platform;\nimport rxhttp.wrapper.OkHttpCompat;\nimport rxhttp.wrapper.callback.IConverter;\nimport rxhttp.wrapper.entity.ParameterizedTypeImpl;\n\n/**\n * User: ljx\n * Date: 2022/10/30\n * Time: 14:44\n */\npublic class Converter {\n\n    public static <T> T convertTo(Response response, Type rawType, Type... types) throws IOException {\n        return convert(response, ParameterizedTypeImpl.get(rawType, types));\n    }\n\n    public static <T> T convertToParameterized(Response response, Type rawType, Type... actualTypes) throws IOException {\n        return convert(response, ParameterizedTypeImpl.getParameterized(rawType, actualTypes));\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T convert(Response response, Type type) throws IOException {\n        ResponseBody body = OkHttpCompat.throwIfFail(response);\n        if (type == ResponseBody.class) {\n            try {\n                return (T) OkHttpCompat.buffer(body);\n            } finally {\n                body.close();\n            }\n        } else if (Platform.get().isAndroid() && type == Bitmap.class) {\n            return (T) BitmapFactory.decodeStream(body.byteStream());\n        } else {\n            boolean needDecodeResult = OkHttpCompat.needDecodeResult(response);\n            IConverter converter = OkHttpCompat.request(response).tag(IConverter.class);\n            return converter.convert(body, type, needDecodeResult);\n        }\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/Converter.kt",
    "content": "package rxhttp.wrapper.utils\n\nimport okhttp3.Response\nimport java.io.IOException\nimport java.lang.reflect.Type\nimport kotlin.reflect.KClass\n\n/**\n * User: ljx\n * Date: 2020/8/15\n * Time: 11:48\n */\n@Throws(IOException::class)\nfun <T> Response.convertTo(rawType: KClass<*>, vararg types: Type): T =\n    convertTo(rawType.java, *types)\n\n@Throws(IOException::class)\nfun <T> Response.convertTo(rawType: Type, vararg types: Type): T =\n    Converter.convertTo(this, rawType, *types)\n\n@Throws(IOException::class)\nfun <T> Response.convertToParameterized(rawType: KClass<*>, vararg actualTypes: Type): T =\n    convertToParameterized(rawType.java, *actualTypes)\n\n@Throws(IOException::class)\nfun <T> Response.convertToParameterized(rawType: Type, vararg actualTypes: Type): T =\n    Converter.convertToParameterized(this, rawType, *actualTypes)\n\n@Throws(IOException::class)\nfun <T> Response.convert(type: Type): T = Converter.convert(this, type)"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/GsonUtil.java",
    "content": "package rxhttp.wrapper.utils;\n\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonDeserializer;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonPrimitive;\nimport com.google.gson.JsonSyntaxException;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.lang.reflect.Type;\n\n/**\n * User: ljx\n * Date: 2018/01/19\n * Time: 10:46\n */\npublic class GsonUtil {\n\n    private static final JsonDeserializer<String> STRING = (json, typeOfT, context) -> {\n        if (json instanceof JsonPrimitive) {\n            return json.getAsString();\n        } else {\n            return json.toString();\n        }\n    };\n    private static final JsonDeserializer<Integer> INTEGER = (json, typeOfT, context) -> {\n        return isEmpty(json) ? 0 : json.getAsInt();\n    };\n    private static final JsonDeserializer<Float> FLOAT = (json, typeOfT, context) -> {\n        return isEmpty(json) ? 0.0f : json.getAsFloat();\n    };\n    private static final JsonDeserializer<Double> DOUBLE = (json, typeOfT, context) -> {\n        return isEmpty(json) ? 0.0 : json.getAsDouble();\n    };\n    private static final JsonDeserializer<Long> LONG = (json, typeOfT, context) -> {\n        return isEmpty(json) ? 0L : json.getAsLong();\n    };\n\n    //return null when an exception occurs\n    @Nullable\n    public static <T> T getObject(String json, Type type) {\n        try {\n            return fromJson(json, type);\n        } catch (Exception ignore) {\n            return null;\n        }\n    }\n\n    //throw exception if deserialization failed\n    @NotNull\n    public static <T> T fromJson(String json, Type type) {\n        Gson gson = buildGson();\n        T t = gson.fromJson(json, type);\n        if (t == null) {\n            throw new JsonSyntaxException(\"The string '\" + json + \"' could not be deserialized to \" + type + \" object\");\n        }\n        return t;\n    }\n\n    public static String toJson(Object object) {\n        return buildGson().toJson(object);\n    }\n\n\n    public static Gson buildGson() {\n        return GsonHolder.gson;\n    }\n\n    private static Gson newGson() {\n        return new GsonBuilder()\n            .disableHtmlEscaping()\n            .registerTypeAdapter(String.class, STRING)\n            .registerTypeAdapter(int.class, INTEGER)\n            .registerTypeAdapter(Integer.class, INTEGER)\n            .registerTypeAdapter(float.class, FLOAT)\n            .registerTypeAdapter(Float.class, FLOAT)\n            .registerTypeAdapter(double.class, DOUBLE)\n            .registerTypeAdapter(Double.class, DOUBLE)\n            .registerTypeAdapter(long.class, LONG)\n            .registerTypeAdapter(Long.class, LONG)\n            .create();\n    }\n\n    private static boolean isEmpty(JsonElement jsonElement) {\n        try {\n            String str = jsonElement.getAsString();\n            return \"\".equals(str) || \"null\".equals(str);\n        } catch (Exception ignore) {\n            return false;\n        }\n    }\n\n    private static final class GsonHolder {\n        static final Gson gson = newGson();\n    }\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/JSONStringer.java",
    "content": "package rxhttp.wrapper.utils;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\n\n\npublic class JSONStringer {\n\n    /**\n     * The output data, containing at most one top-level array or object.\n     */\n    final StringBuilder out = new StringBuilder();\n\n    /**\n     * Unlike the original implementation, this stack isn't limited to 20\n     * levels of nesting.\n     */\n    private final List<Scope> stack = new ArrayList<>();\n\n    /**\n     * A string containing a full set of spaces for a single level of\n     * indentation, or null for no pretty printing.\n     */\n    private final String indent;\n\n    private SerializeCallback mSerializeCallback;\n\n    public JSONStringer() {\n        indent = null;\n    }\n\n    JSONStringer(int indentSpaces) {\n        char[] indentChars = new char[indentSpaces];\n        Arrays.fill(indentChars, ' ');\n        indent = new String(indentChars);\n    }\n\n    public JSONStringer setSerializeCallback(SerializeCallback serializeCallback) {\n        mSerializeCallback = serializeCallback;\n        return this;\n    }\n\n    public JSONStringer write(Collection<?> list) throws JSONException {\n        array();\n        for (Object value : list) {\n            value(value);\n        }\n        endArray();\n        return this;\n    }\n\n    public JSONStringer write(Map<?, ?> map) throws JSONException {\n        object();\n        for (Map.Entry<?, ?> entry : map.entrySet()) {\n            key(entry.getKey().toString()).value(entry.getValue());\n        }\n        endObject();\n        return this;\n    }\n\n    public JSONStringer write(JSONArray jsonArray) throws JSONException {\n        array();\n        int length = jsonArray.length();\n        for (int i = 0; i < length; i++) {\n            value(jsonArray.opt(i));\n        }\n        endArray();\n        return this;\n    }\n\n    public JSONStringer write(JSONObject jsonObject) throws JSONException {\n        object();\n        Iterator<?> iterator = jsonObject.keys();\n        while (iterator.hasNext()) {\n            String key = iterator.next().toString();\n            key(key).value(jsonObject.opt(key));\n        }\n        endObject();\n        return this;\n    }\n\n    /**\n     * Begins encoding a new array. Each call to this method must be paired with\n     * a call to {@link #endArray}.\n     *\n     * @return this stringer.\n     */\n    public JSONStringer array() throws JSONException {\n        return open(Scope.EMPTY_ARRAY, \"[\");\n    }\n\n    /**\n     * Ends encoding the current array.\n     *\n     * @return this stringer.\n     */\n    public JSONStringer endArray() throws JSONException {\n        return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, \"]\");\n    }\n\n    /**\n     * Begins encoding a new object. Each call to this method must be paired\n     * with a call to {@link #endObject}.\n     *\n     * @return this stringer.\n     */\n    public JSONStringer object() throws JSONException {\n        return open(Scope.EMPTY_OBJECT, \"{\");\n    }\n\n    /**\n     * Ends encoding the current object.\n     *\n     * @return this stringer.\n     */\n    public JSONStringer endObject() throws JSONException {\n        return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, \"}\");\n    }\n\n    /**\n     * Enters a new scope by appending any necessary whitespace and the given\n     * bracket.\n     */\n\n    JSONStringer open(Scope empty, String openBracket) throws JSONException {\n        if (stack.isEmpty() && out.length() > 0) {\n            throw new JSONException(\"Nesting problem: multiple top-level roots\");\n        }\n        beforeValue();\n        stack.add(empty);\n        out.append(openBracket);\n        return this;\n    }\n\n    /**\n     * Closes the current scope by appending any necessary whitespace and the\n     * given bracket.\n     */\n\n    JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {\n        Scope context = peek();\n        if (context != nonempty && context != empty) {\n            throw new JSONException(\"Nesting problem\");\n        }\n\n        stack.remove(stack.size() - 1);\n        if (context == nonempty) {\n            newline();\n        }\n        out.append(closeBracket);\n        return this;\n    }\n\n    /**\n     * Returns the value on the top of the stack.\n     */\n\n    private Scope peek() throws JSONException {\n        if (stack.isEmpty()) {\n            throw new JSONException(\"Nesting problem\");\n        }\n        return stack.get(stack.size() - 1);\n    }\n\n    /**\n     * Replace the value on the top of the stack with the given value.\n     */\n\n    private void replaceTop(Scope topOfStack) {\n        stack.set(stack.size() - 1, topOfStack);\n    }\n\n    /**\n     * Encodes {@code value}.\n     *\n     * @param value a {@link Collection}, {@link Map}, String, Boolean,\n     *              Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs}\n     *              or {@link Double#isInfinite() infinities}.\n     * @return this stringer.\n     */\n    public JSONStringer value(Object value) throws JSONException {\n        if (stack.isEmpty()) {\n            throw new JSONException(\"Nesting problem\");\n        }\n\n        if (value instanceof JSONArray) {\n            write((JSONArray) value);\n            return this;\n        } else if (value instanceof JSONObject) {\n            write((JSONObject) value);\n            return this;\n        } else if (value instanceof Collection) {\n            write((Collection<?>) value);\n            return this;\n        } else if (value instanceof Map) {\n            write((Map<?, ?>) value);\n            return this;\n        }\n\n        beforeValue();\n\n        if (value == null\n            || value instanceof Boolean\n            || value == JSONObject.NULL) {\n            out.append(value);\n\n        } else if (value instanceof Number) {\n            out.append(JSONObject.numberToString((Number) value));\n\n        } else if (mSerializeCallback != null) {\n            if (value instanceof String) {\n                string(value.toString());\n            } else {\n                String serialize = mSerializeCallback.serialize(value);\n                out.append(serialize);\n            }\n        } else {\n            string(value.toString());\n        }\n\n        return this;\n    }\n\n    /**\n     * Encodes {@code value} to this stringer.\n     *\n     * @return this stringer.\n     */\n    public JSONStringer value(boolean value) throws JSONException {\n        if (stack.isEmpty()) {\n            throw new JSONException(\"Nesting problem\");\n        }\n        beforeValue();\n        out.append(value);\n        return this;\n    }\n\n    /**\n     * Encodes {@code value} to this stringer.\n     *\n     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or\n     *              {@link Double#isInfinite() infinities}.\n     * @return this stringer.\n     */\n    public JSONStringer value(double value) throws JSONException {\n        if (stack.isEmpty()) {\n            throw new JSONException(\"Nesting problem\");\n        }\n        beforeValue();\n        out.append(JSONObject.numberToString(value));\n        return this;\n    }\n\n    /**\n     * Encodes {@code value} to this stringer.\n     *\n     * @return this stringer.\n     */\n    public JSONStringer value(long value) throws JSONException {\n        if (stack.isEmpty()) {\n            throw new JSONException(\"Nesting problem\");\n        }\n        beforeValue();\n        out.append(value);\n        return this;\n    }\n\n    private void string(String value) {\n        out.append(\"\\\"\");\n        for (int i = 0, length = value.length(); i < length; i++) {\n            char c = value.charAt(i);\n\n            /*\n             * From RFC 4627, \"All Unicode characters may be placed within the\n             * quotation marks except for the characters that must be escaped:\n             * quotation mark, reverse solidus, and the control characters\n             * (U+0000 through U+001F).\"\n             */\n            switch (c) {\n                case '\"':\n                case '\\\\':\n//                case '/':\n                    out.append('\\\\').append(c);\n                    break;\n\n                case '\\t':\n                    out.append(\"\\\\t\");\n                    break;\n\n                case '\\b':\n                    out.append(\"\\\\b\");\n                    break;\n\n                case '\\n':\n                    out.append(\"\\\\n\");\n                    break;\n\n                case '\\r':\n                    out.append(\"\\\\r\");\n                    break;\n\n                case '\\f':\n                    out.append(\"\\\\f\");\n                    break;\n\n                default:\n                    if (c <= 0x1F) {\n                        out.append(String.format(\"\\\\u%04x\", (int) c));\n                    } else {\n                        out.append(c);\n                    }\n                    break;\n            }\n\n        }\n        out.append(\"\\\"\");\n    }\n\n    private void newline() {\n        if (indent == null) {\n            return;\n        }\n\n        out.append(\"\\n\");\n        for (int i = 0; i < stack.size(); i++) {\n            out.append(indent);\n        }\n    }\n\n    /**\n     * Encodes the key (property name) to this stringer.\n     *\n     * @param name the name of the forthcoming value. May not be null.\n     * @return this stringer.\n     */\n    public JSONStringer key(String name) throws JSONException {\n        if (name == null) {\n            throw new JSONException(\"Names must be non-null\");\n        }\n        beforeKey();\n        string(name);\n        return this;\n    }\n\n    /**\n     * Inserts any necessary separators and whitespace before a name. Also\n     * adjusts the stack to expect the key's value.\n     */\n\n    private void beforeKey() throws JSONException {\n        Scope context = peek();\n        if (context == Scope.NONEMPTY_OBJECT) { // first in object\n            out.append(',');\n        } else if (context != Scope.EMPTY_OBJECT) { // not in an object!\n            throw new JSONException(\"Nesting problem\");\n        }\n        newline();\n        replaceTop(Scope.DANGLING_KEY);\n    }\n\n    /**\n     * Inserts any necessary separators and whitespace before a literal value,\n     * inline array, or inline object. Also adjusts the stack to expect either a\n     * closing bracket or another element.\n     */\n\n    private void beforeValue() throws JSONException {\n        if (stack.isEmpty()) {\n            return;\n        }\n\n        Scope context = peek();\n        if (context == Scope.EMPTY_ARRAY) { // first in array\n            replaceTop(Scope.NONEMPTY_ARRAY);\n            newline();\n        } else if (context == Scope.NONEMPTY_ARRAY) { // another in array\n            out.append(',');\n            newline();\n        } else if (context == Scope.DANGLING_KEY) { // value for key\n            out.append(indent == null ? \":\" : \": \");\n            replaceTop(Scope.NONEMPTY_OBJECT);\n        } else if (context != Scope.NULL) {\n            throw new JSONException(\"Nesting problem\");\n        }\n    }\n\n    /**\n     * Returns the encoded JSON string.\n     *\n     * <p>If invoked with unterminated arrays or unclosed objects, this method's\n     * return value is undefined.\n     *\n     * <p><strong>Warning:</strong> although it contradicts the general contract\n     * of {@link Object#toString}, this method returns null if the stringer\n     * contains no data.\n     */\n    @Override\n    public String toString() {\n        return out.length() == 0 ? null : out.toString();\n    }\n\n    /**\n     * Lexical scoping elements within this stringer, necessary to insert the\n     * appropriate separator characters (ie. commas and colons) and to detect\n     * nesting errors.\n     */\n    enum Scope {\n\n        /**\n         * An array with no elements requires no separators or newlines before\n         * it is closed.\n         */\n        EMPTY_ARRAY,\n\n        /**\n         * A array with at least one value requires a comma and newline before\n         * the next element.\n         */\n        NONEMPTY_ARRAY,\n\n        /**\n         * An object with no keys or values requires no separators or newlines\n         * before it is closed.\n         */\n        EMPTY_OBJECT,\n\n        /**\n         * An object whose most recent element is a key. The next element must\n         * be a value.\n         */\n        DANGLING_KEY,\n\n        /**\n         * An object with at least one name/value pair requires a comma and\n         * newline before the next element.\n         */\n        NONEMPTY_OBJECT,\n\n        /**\n         * A special bracketless array needed by JSONStringer.join() and\n         * JSONObject.quote() only. Not used for JSON encoding.\n         */\n        NULL,\n    }\n\n   public interface SerializeCallback {\n       String serialize(Object object);\n   }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/Json.kt",
    "content": "@file:JvmName(\"JsonUtil\")\n\npackage rxhttp.wrapper.utils\n\nimport com.google.gson.JsonArray\nimport com.google.gson.JsonElement\nimport com.google.gson.JsonObject\nimport com.google.gson.JsonPrimitive\nimport com.google.gson.internal.LazilyParsedNumber\nimport java.util.*\n\n/**\n * User: ljx\n * Date: 2020/8/1\n * Time: 17:27\n */\n\nfun JsonElement.toAny(): Any? {\n    return when (this) {\n        is JsonObject -> toMap()\n        is JsonArray -> toList()\n        is JsonPrimitive -> toAny()\n        else -> null\n    }\n}\n\nfun JsonObject.toMap(): Map<String, Any?> {\n    val map: MutableMap<String, Any?> = LinkedHashMap()\n    for ((key, value) in entrySet()) {\n        map[key] = value.toAny()\n    }\n    return map\n}\n\nfun JsonArray.toList(): List<Any?> = map { it.toAny() }\n\nfun JsonPrimitive.toAny(): Any {\n    return when {\n        isNumber -> asNumber.toNumber()\n        isBoolean -> asBoolean\n        else -> asString\n    }\n}\n\n/**\n * 因fastJson、moshi等第三方数据解析工具正常情况下无法对LazilyParsedNumber类型数据，做出正确的序列化操作\n * 故对于正常数据，转为基本类型，对于超大整型、浮点型数据，转为BigInteger、BigDecimal (moshi 需要额外配置，才能对这两个数据类型正常序列化)\n */\nfun Number.toNumber(): Number {\n    return if (this is LazilyParsedNumber) {\n        val number = toString()\n        if (number.contains(\".\")) {\n            val double = toDouble()\n            if (double.toString() == number) {\n                double\n            } else {\n                number.toBigDecimal()\n            }\n        } else {\n            val long = toLong()\n            if (long.toString() == number) {\n                long\n            } else {\n                number.toBigInteger()\n            }\n        }\n    } else this\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/LogTime.java",
    "content": "package rxhttp.wrapper.utils;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * User: ljx\n * Date: 2019-11-30\n * Time: 18:37\n */\npublic class LogTime {\n\n    private final long startNs;\n\n    public LogTime() {\n        this.startNs = System.nanoTime();\n    }\n\n    public long tookMs() {\n        return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/LogUtil.java",
    "content": "package rxhttp.wrapper.utils;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.json.JSONArray;\nimport org.json.JSONObject;\nimport org.json.JSONTokener;\n\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.util.List;\n\nimport kotlin.text.Charsets;\nimport okhttp3.Call;\nimport okhttp3.Cookie;\nimport okhttp3.CookieJar;\nimport okhttp3.Headers;\nimport okhttp3.HttpUrl;\nimport okhttp3.MediaType;\nimport okhttp3.MultipartBody;\nimport okhttp3.MultipartBody.Part;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\nimport okhttp3.internal.http.HttpHeaders;\nimport okio.Buffer;\nimport okio.BufferedSource;\nimport rxhttp.Platform;\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.internal.RxHttpVersion;\nimport rxhttp.wrapper.OkHttpCompat;\nimport rxhttp.wrapper.entity.FileRequestBody;\nimport rxhttp.wrapper.entity.UriRequestBody;\nimport rxhttp.wrapper.exception.ProxyException;\nimport rxhttp.wrapper.progress.ProgressRequestBody;\n\n/**\n * User: ljx\n * Date: 2019/4/1\n * Time: 17:21\n */\npublic class LogUtil {\n\n    private static final String TAG = \"RxHttp\";\n    private static final String TAG_RXJAVA = \"RxHttp-RxJava\";\n\n    private static boolean isDebug = false;\n    //Segmenting logs If the log length is too long\n    private static boolean isSegmentPrint = false;\n    private static int indentSpaces = -1; //json数据缩进空间，默认不缩进\n\n    /**\n     * @param debug        Print a detailed request log if debug is true, Filter `RxHttp` keyword\n     * @param segmentPrint Segment Print\n     * @param indentSpaces Json data is formatted for output if indentSpaces > 0\n     */\n    public static void setDebug(boolean debug, boolean segmentPrint, int indentSpaces) {\n        isDebug = debug;\n        isSegmentPrint = segmentPrint;\n        LogUtil.indentSpaces = indentSpaces;\n    }\n\n    public static boolean isDebug() {\n        return isDebug;\n    }\n\n    public static boolean isSegmentPrint() {\n        return isSegmentPrint;\n    }\n\n    //Print RxJava Throwable\n    public static void logRxJavaError(Throwable throwable) {\n        if (!isDebug) return;\n        String throwableName = throwable.getClass().getName();\n        if (throwableName.equals(\"io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException\")\n            || throwableName.equals(\"io.reactivex.exceptions.OnErrorNotImplementedException\")) {\n            return;\n        }\n        Platform.get().loge(TAG_RXJAVA, throwable);\n    }\n\n    //Print message and throwable\n    public static void log(String message, Throwable throwable) {\n        if (!isDebug) return;\n        Platform.get().loge(TAG, message, throwable);\n    }\n\n    public static void logCall(@Nullable Call call, Throwable e) {\n        if (!isDebug) return;\n        Throwable throwable = call != null ? new ProxyException(call.request(), e) : e;\n        Platform.get().loge(TAG, throwable);\n    }\n\n    public static void log(Throwable throwable) {\n        if (!isDebug) return;\n        Platform.get().loge(TAG, throwable);\n    }\n\n\n    public static void log(String msg) {\n        if (!isDebug()) return;\n        Platform.get().loge(TAG, msg);\n    }\n\n    //Print Request\n    public static void log(@NotNull Request userRequest, CookieJar cookieJar) {\n        if (!isDebug) return;\n        try {\n            Request.Builder requestBuilder = userRequest.newBuilder();\n            StringBuilder builder = new StringBuilder(\"<------ \")\n                .append(RxHttpVersion.userAgent).append(\" \")\n                .append(OkHttpCompat.getOkHttpUserAgent())\n                .append(\" request start ------>\\n\")\n                .append(userRequest.method())\n                .append(\" \").append(userRequest.url());\n            RequestBody body = userRequest.body();\n            if (body != null) {\n                MediaType contentType = body.contentType();\n                if (contentType != null) {\n                    requestBuilder.header(\"Content-Type\", contentType.toString());\n                }\n                long contentLength = body.contentLength();\n                if (contentLength != -1L) {\n                    requestBuilder.header(\"Content-Length\", String.valueOf(contentLength));\n                    requestBuilder.removeHeader(\"Transfer-Encoding\");\n                } else {\n                    requestBuilder.header(\"Transfer-Encoding\", \"chunked\");\n                    requestBuilder.removeHeader(\"Content-Length\");\n                }\n            }\n\n            if (userRequest.header(\"Host\") == null) {\n                requestBuilder.header(\"Host\", hostHeader(userRequest.url()));\n            }\n\n            if (userRequest.header(\"Connection\") == null) {\n                requestBuilder.header(\"Connection\", \"Keep-Alive\");\n            }\n\n            // If we add an \"Accept-Encoding: gzip\" header field we're responsible for also decompressing\n            // the transfer stream.\n            if (userRequest.header(\"Accept-Encoding\") == null\n                && userRequest.header(\"Range\") == null) {\n                requestBuilder.header(\"Accept-Encoding\", \"gzip\");\n            }\n            List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());\n            if (!cookies.isEmpty()) {\n                requestBuilder.header(\"Cookie\", cookieHeader(cookies));\n            }\n            if (userRequest.header(\"User-Agent\") == null) {\n                requestBuilder.header(\"User-Agent\", OkHttpCompat.getOkHttpUserAgent());\n            }\n            builder.append(\"\\n\").append(readHeaders(requestBuilder.build().headers()));\n            if (body != null) {\n                builder.append(\"\\n\");\n                if (bodyHasUnknownEncoding(userRequest.headers())) {\n                    builder.append(\"(binary \")\n                        .append(body.contentLength())\n                        .append(\"-byte encoded body omitted)\");\n                } else {\n                    builder.append(formattingJson(requestBody2Str(body), indentSpaces));\n                }\n            }\n            Platform.get().logd(TAG, builder.toString());\n        } catch (Throwable e) {\n            Platform.get().loge(TAG, new ProxyException(\"Request start log printing failed\", e));\n        }\n    }\n\n    //Print Response\n    public static void log(@NotNull Response response, @NotNull LogTime logTime) {\n        if (!isDebug) return;\n        try {\n            ResponseBody responseBody = response.body();\n            Request request = response.request();\n            long requestCostMs = logTime.tookMs();\n            long readBodyCostMs = 0;\n            String result;\n            if (!promisesBody(response) || responseBody == null) {\n                result = \"No Response Body\";\n            } else if (bodyHasUnknownEncoding(response.headers())) {\n                result = \"(binary \" + responseBody.contentLength() + \"-byte encoded body omitted)\";\n            } else if (!printBody(responseBody)) {\n                result = \"(binary \" + responseBody.contentLength() + \"-byte non-text body omitted)\";\n            } else {\n                result = formattingJson(response2Str(response), indentSpaces);\n                readBodyCostMs = logTime.tookMs() - requestCostMs;\n            }\n            StringBuilder builder = new StringBuilder(\"<------ \")\n                .append(RxHttpVersion.userAgent).append(\" \")\n                .append(OkHttpCompat.getOkHttpUserAgent())\n                .append(\" request end ------>\\n\")\n                .append(request.method()).append(\" \").append(request.url())\n                .append(\"\\n\\n\").append(response.protocol()).append(\" \")\n                .append(response.code()).append(\" \").append(response.message())\n                .append(\" \").append(requestCostMs).append(\"ms\")\n                .append(readBodyCostMs > 0 ? \" \" + readBodyCostMs + \"ms\" : \"\")\n                .append(\"\\n\").append(readHeaders(response.headers()))\n                .append(\"\\n\").append(result);\n            Platform.get().logi(TAG, builder.toString());\n        } catch (Throwable e) {\n            Platform.get().loge(TAG, new ProxyException(\"Request end log printing failed\", e));\n        }\n    }\n\n    private static String requestBody2Str(@NotNull RequestBody body) throws IOException {\n        if (body instanceof ProgressRequestBody) {\n            body = ((ProgressRequestBody) body).getRequestBody();\n        }\n        if (body instanceof MultipartBody) {\n            return multipartBody2Str((MultipartBody) body);\n        }\n        long contentLength = -1;\n        try {\n            contentLength = body.contentLength();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        if (body instanceof FileRequestBody) {\n            return \"(binary \" + contentLength + \"-byte file body omitted)\";\n        } else if (body instanceof UriRequestBody) {\n            return \"(binary \" + contentLength + \"-byte uri body omitted)\";\n        } else if (versionGte3140() && body.isDuplex()) {\n            return \"(binary \" + contentLength + \"-byte duplex body omitted)\";\n        } else if (versionGte3140() && body.isOneShot()) {\n            return \"(binary \" + contentLength + \"-byte one-shot body omitted)\";\n        } else {\n            Buffer buffer = new Buffer();\n            body.writeTo(buffer);\n            if (!isProbablyUtf8(buffer)) {\n                return \"(binary \" + body.contentLength() + \"-byte body omitted)\";\n            } else {\n                return buffer.readString(getCharset(body));\n            }\n        }\n    }\n\n    private static String multipartBody2Str(MultipartBody multipartBody) {\n        final byte[] colonSpace = {':', ' '};\n        final byte[] CRLF = {'\\r', '\\n'};\n        final byte[] dashDash = {'-', '-'};\n        Buffer sink = new Buffer();\n        for (Part part : multipartBody.parts()) {\n            Headers headers = part.headers();\n            RequestBody body = part.body();\n            sink.write(dashDash)\n                .writeUtf8(multipartBody.boundary())\n                .write(CRLF);\n            if (headers != null) {\n                for (int i = 0, size = headers.size(); i < size; i++) {\n                    sink.writeUtf8(headers.name(i))\n                        .write(colonSpace)\n                        .writeUtf8(headers.value(i))\n                        .write(CRLF);\n                }\n            }\n            MediaType contentType = body.contentType();\n            if (contentType != null) {\n                sink.writeUtf8(\"Content-Type: \")\n                    .writeUtf8(contentType.toString())\n                    .write(CRLF);\n            }\n            long contentLength = -1;\n            try {\n                contentLength = body.contentLength();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n            sink.writeUtf8(\"Content-Length: \")\n                .writeDecimalLong(contentLength)\n                .write(CRLF);\n\n            if (body instanceof MultipartBody) {\n                sink.write(CRLF)\n                    .writeUtf8(multipartBody2Str((MultipartBody) body));\n            } else if (body instanceof FileRequestBody) {\n                sink.writeUtf8(\"(binary \" + contentLength + \"-byte file body omitted)\");\n            } else if (body instanceof UriRequestBody) {\n                sink.writeUtf8(\"(binary \" + contentLength + \"-byte uri body omitted)\");\n            } else if (versionGte3140() && body.isDuplex()) {\n                sink.writeUtf8(\"(binary \" + contentLength + \"-byte duplex body omitted)\");\n            } else if (versionGte3140() && body.isOneShot()) {\n                sink.writeUtf8(\"(binary \" + contentLength + \"-byte one-shot body omitted)\");\n            } else if (contentLength > 1024) {\n                sink.writeUtf8(\"(binary \" + contentLength + \"-byte body omitted)\");\n            } else {\n                try {\n                    body.writeTo(sink);\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n            if (contentLength > 0) sink.write(CRLF);\n            sink.write(CRLF);\n        }\n        sink.write(dashDash)\n            .writeUtf8(multipartBody.boundary())\n            .write(dashDash);\n        return sink.readString(getCharset(multipartBody));\n    }\n\n    private static boolean versionGte3140() {\n        return OkHttpCompat.okHttpVersionCompare(\"3.14.0\") >= 0;\n    }\n\n    private static boolean versionGte400() {\n        return OkHttpCompat.okHttpVersionCompare(\"4.0.0\") >= 0;\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    private static String response2Str(Response response) throws IOException {\n        ResponseBody body = response.body();\n        boolean onResultDecoder = OkHttpCompat.needDecodeResult(response);\n\n        BufferedSource source = body.source();\n        source.request(Long.MAX_VALUE); // Buffer the entire body.\n        Buffer buffer = source.buffer();\n        String result;\n        if (isProbablyUtf8(buffer)) {\n            result = buffer.clone().readString(getCharset(body));\n            if (onResultDecoder) {\n                result = RxHttpPlugins.onResultDecoder(result);\n            }\n        } else {\n            result = \"(binary \" + buffer.size() + \"-byte body omitted)\";\n        }\n        return result;\n    }\n\n    //format json\n    private static String formattingJson(String json, int indentSpaces) {\n        if (indentSpaces >= 0) {\n            try {\n                JSONTokener jsonTokener = new JSONTokener(json);\n                if (json.startsWith(\"[\")) {\n                    JSONArray jsonObject = new JSONArray(jsonTokener);\n                    if (jsonTokener.more()) {\n                        //https://github.com/liujingxing/rxhttp/issues/463\n                        return json;\n                    }\n                    return new JSONStringer(indentSpaces).write(jsonObject).toString();\n                } else if (json.startsWith(\"{\")) {\n                    JSONObject jsonObject = new JSONObject(jsonTokener);\n                    if (jsonTokener.more()) {\n                        //https://github.com/liujingxing/rxhttp/issues/463\n                        return json;\n                    }\n                    return new JSONStringer(indentSpaces).write(jsonObject).toString();\n                }\n            } catch (Throwable ignore) {\n                return json;\n            }\n        }\n        return json;\n    }\n\n    private static boolean isProbablyUtf8(Buffer buffer) {\n        try {\n            Buffer prefix = new Buffer();\n            long byteCount = buffer.size() < 64 ? buffer.size() : 64;\n            buffer.copyTo(prefix, 0, byteCount);\n            for (int i = 0; i < 16; i++) {\n                if (prefix.exhausted()) {\n                    break;\n                }\n                int codePoint = prefix.readUtf8CodePoint();\n                if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {\n                    return false;\n                }\n            }\n            return true;\n        } catch (EOFException e) {\n            return false; // Truncated UTF-8 sequence.\n        }\n    }\n\n    private static Charset getCharset(RequestBody requestBody) {\n        MediaType mediaType = requestBody.contentType();\n        return mediaType != null ? mediaType.charset(Charsets.UTF_8) : Charsets.UTF_8;\n    }\n\n    private static Charset getCharset(ResponseBody responseBody) {\n        MediaType mediaType = responseBody.contentType();\n        return mediaType != null ? mediaType.charset(Charsets.UTF_8) : Charsets.UTF_8;\n    }\n\n\n    private static String hostHeader(HttpUrl url) {\n        String host = url.host().contains(\":\")\n            ? \"[\" + url.host() + \"]\"\n            : url.host();\n        return host + \":\" + url.port();\n    }\n\n    /**\n     * Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}.\n     */\n    private static String cookieHeader(List<Cookie> cookies) {\n        StringBuilder cookieHeader = new StringBuilder();\n        for (int i = 0, size = cookies.size(); i < size; i++) {\n            if (i > 0) {\n                cookieHeader.append(\"; \");\n            }\n            Cookie cookie = cookies.get(i);\n            cookieHeader.append(cookie.name()).append('=').append(cookie.value());\n        }\n        return cookieHeader.toString();\n    }\n\n    private static boolean bodyHasUnknownEncoding(Headers headers) {\n        String contentEncoding = headers.get(\"Content-Encoding\");\n        return contentEncoding != null\n            && !contentEncoding.equalsIgnoreCase(\"identity\")\n            && !contentEncoding.equalsIgnoreCase(\"gzip\");\n    }\n\n    private static boolean printBody(@NotNull ResponseBody responseBody) {\n        MediaType mediaType = responseBody.contentType();\n        if (mediaType != null) {\n            String type = mediaType.type();\n            String subtype = mediaType.subtype();\n\n            if (type.equalsIgnoreCase(\"text\") || subtype.equalsIgnoreCase(\"json\")\n                || subtype.equalsIgnoreCase(\"xml\"))\n                return true;\n\n            if (type.equalsIgnoreCase(\"image\") || type.equalsIgnoreCase(\"audio\")\n                || type.equalsIgnoreCase(\"video\") || subtype.equalsIgnoreCase(\"zip\"))\n                return false;\n        }\n        //Considering that the contentType may be misused, try to print if the contentLength is less than 1M\n        return mediaType != null && mediaType.charset() != null && responseBody.contentLength() < 1024 * 1024;\n    }\n\n    /**\n     * Since OkHttp v4.9.2, the {@link Headers#toString()} method, will hide sensitive information\n     * in the Authorization, Cookie, proxy-authorization, and set-cookie headers, so they are read manually\n     *\n     * @param headers okhttp3.Headers\n     * @return String\n     */\n    private static String readHeaders(Headers headers) {\n        StringBuilder builder = new StringBuilder();\n        int size = headers.size();\n        for (int i = 0; i < size; i++) {\n            builder.append(headers.name(i))\n                .append(\": \")\n                .append(headers.value(i))\n                .append(\"\\n\");\n        }\n        return builder.toString();\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    private static boolean promisesBody(Response response) {\n        //The `HttpHeaders.hasBody` method was removed from okhttp 4.0.0, but was restored and deprecated in 4.0.1\n        return versionGte400() ? HttpHeaders.promisesBody(response) : HttpHeaders.hasBody(response);\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/PathEncoder.kt",
    "content": "package rxhttp.wrapper.utils\n\nimport okio.Buffer\n\n\n/**\n * User: ljx\n * Date: 2021/10/24\n * Time: 21:25\n */\nprivate val HEX_DIGITS =\n    charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')\n\nprivate const val PATH_SEGMENT_ALWAYS_ENCODE_SET = \" \\\"<>^`{}|\\\\?#\"\n\ninternal fun String.canonicalizeForPath(alreadyEncoded: Boolean): String {\n    var codePoint: Int\n    var i = 0\n    val limit = length\n    while (i < limit) {\n        codePoint = codePointAt(i)\n        if (codePoint < 0x20 ||\n            codePoint >= 0x7f ||\n            codePoint.toChar() in PATH_SEGMENT_ALWAYS_ENCODE_SET ||\n            !alreadyEncoded && (codePoint == '/'.code || codePoint == '%'.code)\n        ) {\n            // Slow path: the character at i requires encoding!\n            val out = Buffer()\n            out.writeUtf8(this, 0, i)\n            out.canonicalizeForPath(this, i, limit, alreadyEncoded)\n            return out.readUtf8()\n        }\n        i += Character.charCount(codePoint)\n    }\n\n    // Fast path: no characters required encoding.\n    return this\n}\n\nprivate fun Buffer.canonicalizeForPath(\n    input: String,\n    pos: Int,\n    limit: Int,\n    alreadyEncoded: Boolean\n) {\n    var utf8Buffer: Buffer? = null // Lazily allocated.\n    var codePoint: Int\n    var i = pos\n    while (i < limit) {\n        codePoint = input.codePointAt(i)\n        if (alreadyEncoded && (codePoint == '\\t'.code || codePoint == '\\n'.code ||\n                codePoint == '\\u000c'.code || codePoint == '\\r'.code)\n        ) {\n            // Skip this character.\n        } else if (codePoint < 0x20 ||\n            codePoint >= 0x7f ||\n            codePoint.toChar() in PATH_SEGMENT_ALWAYS_ENCODE_SET ||\n            !alreadyEncoded && (codePoint == '/'.code || codePoint == '%'.code)\n        ) {\n            // Percent encode this character.\n            if (utf8Buffer == null) {\n                utf8Buffer = Buffer()\n            }\n            utf8Buffer.writeUtf8CodePoint(codePoint)\n            while (!utf8Buffer.exhausted()) {\n                val b: Int = utf8Buffer.readByte().toInt() and 0xff\n                writeByte('%'.code)\n                writeByte(HEX_DIGITS[b shr 4 and 0xf].code)\n                writeByte(HEX_DIGITS[b and 0xf].code)\n            }\n        } else {\n            // This character doesn't need encoding. Just copy it over.\n            writeUtf8CodePoint(codePoint)\n        }\n        i += Character.charCount(codePoint)\n    }\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/Speeder.kt",
    "content": "package rxhttp.wrapper.utils\n\nimport java.util.concurrent.LinkedBlockingQueue\n\n/**\n * User: ljx\n * Date: 2024/6/16\n * Time: 16:21\n */\nclass Speeder(\n    private var lastLength: Long,\n    private var lastTime: Long,\n) {\n\n    private var lastSpeed: Long? = null\n    //缓存近5秒内每秒的速速，回调时，取平均值，避免抖动过大\n    private val queue = LinkedBlockingQueue<Long>(5)\n\n    fun updateSpeed(currentLength: Long, currentTime: Long, complete: Boolean = false): Long {\n        var diff = currentTime - lastTime\n        //速度最小更新周期为1s，第一次跟最后一次不受时长限制\n        if (diff >= 1000 || complete || queue.isEmpty()) {\n            if (diff == 0L) diff = 1L\n            val speed = (currentLength - lastLength) * 1000 / diff\n            while (!queue.offer(speed)) {\n                queue.poll()\n            }\n            lastSpeed = null\n            lastLength = currentLength\n            lastTime = currentTime\n        }\n        return averageSpeed()\n    }\n\n    private fun averageSpeed() = lastSpeed ?: queue.average().toLong().also { lastSpeed = it }\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/TypeUtil.java",
    "content": "package rxhttp.wrapper.utils;\n\nimport org.jetbrains.annotations.Nullable;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\n\nimport rxhttp.wrapper.entity.OkResponse;\n\n/**\n * User: ljx\n * Date: 2022/10/23\n * Time: 19:20\n */\npublic class TypeUtil {\n\n    public static Type[] getActualTypeParameters(Class<?> clazz) {\n        Type superclass = clazz.getGenericSuperclass();\n        if (superclass instanceof ParameterizedType) {\n            ParameterizedType parameterized = (ParameterizedType) superclass;\n            return parameterized.getActualTypeArguments();\n        }\n        throw new RuntimeException(\"Missing type parameter.\");\n    }\n\n    @Nullable\n    public static Type getActualType(Type type) {\n        if (type instanceof ParameterizedType) {\n            ParameterizedType parameterized = (ParameterizedType) type;\n            if (parameterized.getRawType() == OkResponse.class) {\n                return parameterized.getActualTypeArguments()[0];\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/Uri.kt",
    "content": "@file:JvmName(\"UriUtil\")\n\npackage rxhttp.wrapper.utils\n\nimport android.content.ContentResolver\nimport android.content.ContentUris\nimport android.content.Context\nimport android.net.Uri\nimport android.provider.MediaStore\nimport java.io.File\nimport java.io.FileNotFoundException\n\n/**\n * User: ljx\n * Date: 2020/9/26\n * Time: 14:55\n */\n\nfun Uri?.length(context: Context): Long = length(context.contentResolver)\n\n//return The size of the media item, return -1 if does not exist, might block.\nfun Uri?.length(contentResolver: ContentResolver): Long {\n    if (this == null) return -1L\n    if (scheme == ContentResolver.SCHEME_FILE) {\n        return File(path).length()\n    }\n    return try {\n        contentResolver.openFileDescriptor(this, \"r\").use { it?.statSize ?: -1 }\n    } catch (e: FileNotFoundException) {\n        -1L\n    }\n}\n\nfun Uri.displayName(context: Context): String? =\n    if (scheme == ContentResolver.SCHEME_FILE) {\n        lastPathSegment\n    } else {\n        getColumnValue(context.contentResolver, MediaStore.MediaColumns.DISPLAY_NAME)\n    }\n\n//Return the value of the specified column，return null if does not exist\ninternal fun Uri.getColumnValue(contentResolver: ContentResolver, columnName: String): String? {\n    return contentResolver.query(this, arrayOf(columnName),\n        null, null, null)?.use {\n        if (it.moveToFirst()) it.getString(0) else null\n    }\n}\n\n//find the Uri by filename and relativePath, return null if find fail.  RequiresApi 29\nfun Uri.query(context: Context, filename: String?, relativePath: String?): Uri? {\n    if (filename.isNullOrEmpty() || relativePath.isNullOrEmpty()) return null\n    val realRelativePath = relativePath.let {\n        //Remove the prefix slash if it exists\n        if (it.startsWith(\"/\")) it.substring(1) else it\n    }.let {\n        //Suffix adds a slash if it does not exist\n        if (it.endsWith(\"/\")) it else \"$it/\"\n    }\n    val columnNames = arrayOf(\n        MediaStore.MediaColumns._ID,\n    )\n    return context.contentResolver.query(this, columnNames,\n        \"relative_path=? AND _display_name=?\", arrayOf(realRelativePath, filename), null)?.use {\n        if (it.moveToFirst()) {\n            val uriId = it.getLong(0)\n            ContentUris.withAppendedId(this, uriId)\n        } else null\n    }\n}"
  },
  {
    "path": "rxhttp/src/main/java/rxhttp/wrapper/utils/Utils.kt",
    "content": "@file:JvmName(\"Utils\")\npackage rxhttp.wrapper.utils\n\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport okhttp3.Call\nimport okhttp3.Callback\nimport okhttp3.Response\nimport okio.Buffer\nimport okio.Source\nimport rxhttp.wrapper.OkHttpCompat\nimport rxhttp.wrapper.entity.ParameterizedTypeImpl\nimport rxhttp.wrapper.parse.Parser\nimport java.io.Closeable\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.InterruptedIOException\nimport java.io.OutputStream\nimport java.lang.reflect.ParameterizedType\nimport java.lang.reflect.Type\nimport java.util.concurrent.TimeUnit\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\nimport kotlin.reflect.KClass\nimport kotlin.reflect.javaType\nimport kotlin.reflect.typeOf\n\n/**\n * User: ljx\n * Date: 2022/9/22\n * Time: 23:47\n */\ninternal suspend fun <T> Call.await(parser: Parser<T>): T {\n    return suspendCancellableCoroutine { continuation ->\n        continuation.invokeOnCancellation {\n            cancel()\n        }\n        enqueue(object : Callback {\n\n            override fun onResponse(call: Call, response: Response) {\n                try {\n                    continuation.resume(parser.onParse(response))\n                } catch (t: Throwable) {\n                    continuation.resumeWithException(t)\n                }\n            }\n\n            override fun onFailure(call: Call, e: IOException) {\n                continuation.resumeWithException(e)\n            }\n        })\n    }\n}\n\nfun Closeable.closeQuietly() {\n    try {\n        close()\n    } catch (rethrown: RuntimeException) {\n        throw rethrown\n    } catch (_: Exception) {\n    }\n}\n\nfun Source.discard(\n    timeout: Int,\n    timeUnit: TimeUnit,\n): Boolean =\n    try {\n        this.skipAll(timeout, timeUnit)\n    } catch (_: IOException) {\n        false\n    }\n\n\n@Throws(IOException::class)\ninternal fun Source.skipAll(\n    duration: Int,\n    timeUnit: TimeUnit,\n): Boolean {\n    val nowNs = System.nanoTime()\n    val originalDurationNs =\n        if (timeout().hasDeadline()) {\n            timeout().deadlineNanoTime() - nowNs\n        } else {\n            Long.MAX_VALUE\n        }\n    timeout().deadlineNanoTime(nowNs + minOf(originalDurationNs, timeUnit.toNanos(duration.toLong())))\n    return try {\n        val skipBuffer = Buffer()\n        while (read(skipBuffer, 8192) != -1L) {\n            skipBuffer.clear()\n        }\n        true // Success! The source has been exhausted.\n    } catch (_: InterruptedIOException) {\n        false // We ran out of time before exhausting the source.\n    } finally {\n        if (originalDurationNs == Long.MAX_VALUE) {\n            timeout().clearDeadline()\n        } else {\n            timeout().deadlineNanoTime(nowNs + originalDurationNs)\n        }\n    }\n}\n\nprivate const val LENGTH_BYTE = 8 * 1024\n\n@Throws(IOException::class)\ninternal fun InputStream.writeTo(\n    outStream: OutputStream,\n    progress: ((Int) -> Unit)? = null\n) {\n    try {\n        val bytes = ByteArray(LENGTH_BYTE)\n        var readLength: Int\n        while (read(bytes, 0, bytes.size).also { readLength = it } != -1) {\n            outStream.write(bytes, 0, readLength)\n            progress?.invoke(readLength)\n        }\n    } finally {\n        close(this, outStream)\n    }\n}\n\ninternal fun close(vararg closeables: Closeable?) {\n    for (closeable in closeables) {\n        if (closeable == null) continue\n        try {\n            closeable.close()\n        } catch (ignored: IOException) {\n        }\n    }\n}\n\ninternal fun Response.isPartialContent() = OkHttpCompat.isPartialContent(this)\n\nfun KClass<*>.parameterizedBy(vararg typeArguments: Type): ParameterizedType=\n    ParameterizedTypeImpl.getParameterized(java, *typeArguments)\n\n@OptIn(ExperimentalStdlibApi::class)\ninline fun <reified T> javaTypeOf(): Type = typeOf<T>().javaType\n\ninternal val Type.wrapType: Type\n    get() {\n        val type = this\n        if (type !is Class<*> || !type.isPrimitive) return type\n        return when (type.name) {\n            \"boolean\" -> java.lang.Boolean::class.java\n            \"char\" -> java.lang.Character::class.java\n            \"byte\" -> java.lang.Byte::class.java\n            \"short\" -> java.lang.Short::class.java\n            \"int\" -> java.lang.Integer::class.java\n            \"float\" -> java.lang.Float::class.java\n            \"long\" -> java.lang.Long::class.java\n            \"double\" -> java.lang.Double::class.java\n            \"void\" -> Void::class.java\n            else -> type\n        }\n    }"
  },
  {
    "path": "rxhttp/src/main/java-templates/rxhttp/internal/RxHttpVersion.kt",
    "content": "@file:JvmName(\"RxHttpVersion\")\n\npackage rxhttp.internal\n\n/**\n * User: ljx\n * Date: 2020/5/17\n * Time: 18:29\n */\nconst val userAgent = \"rxhttp/$projectVersion\""
  },
  {
    "path": "rxhttp/src/test/java/rxhttp/wrapper/entity/ParameterizedTypeImplTest.java",
    "content": "package rxhttp.wrapper.entity;\n\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * User: ljx\n * Date: 2023/7/3\n * Time: 15:54\n */\npublic class ParameterizedTypeImplTest {\n\n    @Test\n    public void testGet() {\n        //boolean.class\n        ParameterizedType parameterizedType = ParameterizedTypeImpl.get(List.class, boolean.class);\n        Type[] types = parameterizedType.getActualTypeArguments();\n        Assert.assertEquals(1, types.length);\n        Assert.assertEquals(Boolean.class, types[0]);\n        Assert.assertEquals(List.class, parameterizedType.getRawType());\n\n        //char.class\n        parameterizedType = ParameterizedTypeImpl.get(List.class, char.class);\n        types = parameterizedType.getActualTypeArguments();\n        Assert.assertEquals(Character.class, types[0]);\n\n        //byte.class\n        parameterizedType = ParameterizedTypeImpl.get(List.class, byte.class);\n        types = parameterizedType.getActualTypeArguments();\n        Assert.assertEquals(Byte.class, types[0]);\n\n        //short.class\n        parameterizedType = ParameterizedTypeImpl.get(List.class, short.class);\n        types = parameterizedType.getActualTypeArguments();\n        Assert.assertEquals(Short.class, types[0]);\n\n        //int.class\n        parameterizedType = ParameterizedTypeImpl.get(List.class, int.class);\n        types = parameterizedType.getActualTypeArguments();\n        Assert.assertEquals(Integer.class, types[0]);\n\n        //long.class\n        parameterizedType = ParameterizedTypeImpl.get(List.class, long.class);\n        types = parameterizedType.getActualTypeArguments();\n        Assert.assertEquals(Long.class, types[0]);\n\n        //float.class\n        parameterizedType = ParameterizedTypeImpl.get(List.class, float.class);\n        types = parameterizedType.getActualTypeArguments();\n        Assert.assertEquals(Float.class, types[0]);\n\n        //double.class\n        parameterizedType = ParameterizedTypeImpl.get(List.class, double.class);\n        types = parameterizedType.getActualTypeArguments();\n        Assert.assertEquals(Double.class, types[0]);\n\n        //void.class\n        parameterizedType = ParameterizedTypeImpl.get(List.class, void.class);\n        types = parameterizedType.getActualTypeArguments();\n        Assert.assertEquals(Void.class, types[0]);\n    }\n\n    @Test\n    public void getParameterized() {\n        ParameterizedType parameterizedType = ParameterizedTypeImpl.getParameterized(Map.class, int.class, long.class);\n        Type[] types = parameterizedType.getActualTypeArguments();\n        Assert.assertEquals(2, types.length);\n        Assert.assertEquals(Integer.class, types[0]);\n        Assert.assertEquals(Long.class, types[1]);\n        Assert.assertEquals(Map.class, parameterizedType.getRawType());\n    }\n}"
  },
  {
    "path": "rxhttp-annotation/build.gradle",
    "content": "plugins {\n    id 'java-library'\n}\napply from: '../maven.gradle'\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n}\n\njava {\n    sourceCompatibility = \"$jdk_version\"\n    targetCompatibility = \"$jdk_version\"\n}\n"
  },
  {
    "path": "rxhttp-annotation/src/main/java/rxhttp/wrapper/annotation/Converter.java",
    "content": "package rxhttp.wrapper.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.CLASS)\npublic @interface Converter {\n\n    String name() default \"\";\n\n    //通过该方法将生成RxXxxHttp类，通过该类发请求，将默认使用指定的IConverter对象\n    String className() default \"\";\n}\n"
  },
  {
    "path": "rxhttp-annotation/src/main/java/rxhttp/wrapper/annotation/DefaultDomain.java",
    "content": "package rxhttp.wrapper.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 默认域名使用该注解\n */\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.CLASS)\npublic @interface DefaultDomain {\n}\n"
  },
  {
    "path": "rxhttp-annotation/src/main/java/rxhttp/wrapper/annotation/Domain.java",
    "content": "package rxhttp.wrapper.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.CLASS)\npublic @interface Domain {\n\n    String name() default \"\";\n\n    //通过该方法将生成RxXxxHttp类，通过该类发请求，将默认使用指定的baseUrl\n    String className() default \"\";\n}\n"
  },
  {
    "path": "rxhttp-annotation/src/main/java/rxhttp/wrapper/annotation/OkClient.java",
    "content": "package rxhttp.wrapper.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 通过该注解，会在RxHttp类中，生成一个方法，用于为某个请求指定单独的OkHttpClient对象，方法取名规则: set+注解上指定的名称\n */\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.CLASS)\npublic @interface OkClient {\n\n    String name() default \"\";\n\n    //通过该方法将生成RxXxxHttp类，通过该类发请求，将默认使用指定的OkHttpClient对象\n    String className() default \"\";\n}\n"
  },
  {
    "path": "rxhttp-annotation/src/main/java/rxhttp/wrapper/annotation/Param.java",
    "content": "package rxhttp.wrapper.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.CLASS)\npublic @interface Param {\n\n    String methodName();\n}\n"
  },
  {
    "path": "rxhttp-annotation/src/main/java/rxhttp/wrapper/annotation/Parser.java",
    "content": "package rxhttp.wrapper.annotation;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.CLASS)\npublic @interface Parser {\n\n    /**\n     * @return parser name\n     */\n    String name();\n\n    /**\n     * 该参数生效条件：\n     * 1、解析器onParse方法返回类型泛型数量有且仅有1个\n     * 2、项目有依赖RxJava\n     * <p>\n     * 生效后，仅会在BaseRxHttp类下生成toObservableXxx方法\n     *\n     * @return Class数组\n     */\n    Class<?>[] wrappers() default {};\n}\n"
  },
  {
    "path": "rxhttp-compiler/build.gradle",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    id 'org.jetbrains.kotlin.jvm'\n    id 'kotlin-kapt'\n}\napply from: '../maven.gradle'\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    implementation libs.javapoet\n    implementation libs.kotlinpoet\n    implementation libs.kotlinpoet.ksp\n    implementation libs.kotlinpoet.javapoet\n//    implementation libs.rxhttp.annotation\n    implementation projects.rxhttpAnnotation\n    implementation libs.symbol.processing.api\n}\n\njava {\n    sourceCompatibility = \"$jdk_version\"\n    targetCompatibility = \"$jdk_version\"\n}\n\nkotlin {\n    compilerOptions {\n        jvmTarget = JvmTarget.JVM_1_8\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/Constants.kt",
    "content": "package com.rxhttp.compiler\n\nimport com.squareup.javapoet.ArrayTypeName\nimport com.squareup.javapoet.ClassName\nimport com.squareup.javapoet.TypeName\nimport com.squareup.kotlinpoet.ARRAY\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\n\n/**\n * User: ljx\n * Date: 2021/11/23\n * Time: 16:10\n */\n\nconst val RxHttp = \"RxHttp\"\n\nconst val defaultPackageName = \"rxhttp.wrapper.param\"\nconst val rxhttp_rxjava = \"rxhttp_rxjava\"\nconst val rxhttp_package = \"rxhttp_package\"\nconst val rxhttp_incremental = \"rxhttp_incremental\"\nconst val rxhttp_debug = \"rxhttp_debug\"\nconst val rxhttp_android_platform = \"rxhttp_android_platform\"\n\nval rxhttpKClass = com.squareup.kotlinpoet.ClassName(rxHttpPackage, RxHttp)\nval rxhttpClass: ClassName = ClassName.get(rxHttpPackage, RxHttp)\n\nval J_TYPE: TypeName = ClassName.bestGuess(\"java.lang.reflect.Type\")\nval J_ARRAY_TYPE: TypeName = ArrayTypeName.of(J_TYPE)\n\nval K_TYPE = com.squareup.kotlinpoet.ClassName(\"java.lang.reflect\", \"Type\")\nval K_ARRAY_TYPE = ARRAY.parameterizedBy(K_TYPE)\n"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/KaptProcessor.kt",
    "content": "package com.rxhttp.compiler\n\nimport com.rxhttp.compiler.kapt.BaseRxHttpGenerator\nimport com.rxhttp.compiler.kapt.ClassHelper\nimport com.rxhttp.compiler.kapt.ConverterVisitor\nimport com.rxhttp.compiler.kapt.DefaultDomainVisitor\nimport com.rxhttp.compiler.kapt.DomainVisitor\nimport com.rxhttp.compiler.kapt.OkClientVisitor\nimport com.rxhttp.compiler.kapt.ParamsVisitor\nimport com.rxhttp.compiler.kapt.ParserVisitor\nimport com.rxhttp.compiler.kapt.RxHttpGenerator\nimport com.rxhttp.compiler.kapt.RxHttpWrapper\nimport rxhttp.wrapper.annotation.Converter\nimport rxhttp.wrapper.annotation.DefaultDomain\nimport rxhttp.wrapper.annotation.Domain\nimport rxhttp.wrapper.annotation.OkClient\nimport rxhttp.wrapper.annotation.Param\nimport rxhttp.wrapper.annotation.Parser\nimport javax.annotation.processing.AbstractProcessor\nimport javax.annotation.processing.Filer\nimport javax.annotation.processing.Messager\nimport javax.annotation.processing.ProcessingEnvironment\nimport javax.annotation.processing.RoundEnvironment\nimport javax.lang.model.SourceVersion\nimport javax.lang.model.element.TypeElement\nimport javax.lang.model.element.VariableElement\nimport javax.lang.model.util.Elements\nimport javax.lang.model.util.Types\nimport javax.tools.Diagnostic\n\n\n/**\n * User: ljx\n * Date: 2019/3/21\n * Time: 20:36\n */\nopen class KaptProcessor : AbstractProcessor() {\n\n    private lateinit var types: Types\n    private lateinit var logger: Messager\n    private lateinit var filer: Filer\n    private lateinit var elementUtils: Elements\n    private var debug = false\n    private var processed = false\n    private var incremental = true\n    private var androidPlatform = true\n\n    @Synchronized\n    override fun init(processingEnvironment: ProcessingEnvironment) {\n        super.init(processingEnvironment)\n        types = processingEnvironment.typeUtils\n        logger = processingEnvironment.messager\n        filer = processingEnvironment.filer\n        elementUtils = processingEnvironment.elementUtils\n        val options = processingEnvironment.options\n        rxHttpPackage = options[rxhttp_package] ?: defaultPackageName\n        incremental = (options[rxhttp_incremental] ?: \"true\").toBoolean()\n        debug = options[rxhttp_debug].toBoolean()\n        androidPlatform = (options[rxhttp_android_platform] ?: \"true\").toBoolean()\n        initRxJavaVersion(getRxJavaVersion(options))\n    }\n\n    override fun getSupportedAnnotationTypes(): Set<String> {\n        return LinkedHashSet<String>().apply {\n            add(Param::class.java.canonicalName)\n            add(Parser::class.java.canonicalName)\n            add(Converter::class.java.canonicalName)\n            add(Domain::class.java.canonicalName)\n            add(DefaultDomain::class.java.canonicalName)\n            add(OkClient::class.java.canonicalName)\n        }\n    }\n\n    override fun getSupportedOptions(): MutableSet<String> {\n        return mutableSetOf(\n            rxhttp_rxjava, rxhttp_package,\n            rxhttp_incremental, rxhttp_debug, rxhttp_android_platform\n        ).apply {\n            if (incremental)\n                add(\"org.gradle.annotation.processing.aggregating\")\n        }\n    }\n\n    open fun getRxJavaVersion(map: Map<String, String>) = map[rxhttp_rxjava]\n\n    open fun isAndroidPlatform() = true\n\n    override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported()\n\n    override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {\n        if (debug) {\n            val log = \"\"\"\n                process isOver = ${roundEnv.processingOver()}     \n                processed = $processed                \n                rootElements.size = ${roundEnv.rootElements.size}\n                annotations = $annotations  \n            \"\"\".trimIndent()\n            logger.printMessage(Diagnostic.Kind.WARNING, log)\n        }\n        if (annotations.isEmpty() || processed) return true\n        ClassHelper(isAndroidPlatform() && androidPlatform).generatorStaticClass(filer)\n        try {\n            val rxHttpWrapper = RxHttpWrapper(logger)\n\n            val paramsVisitor = ParamsVisitor(logger).apply {\n                roundEnv.getElementsAnnotatedWith(Param::class.java).forEach {\n                    val typeElement = it as TypeElement\n                    add(typeElement, types)\n                    rxHttpWrapper.add(typeElement)\n                }\n            }\n\n            val parserVisitor = ParserVisitor(logger).apply {\n                roundEnv.getElementsAnnotatedWith(Parser::class.java).forEach {\n                    val typeElement = it as TypeElement\n                    add(typeElement, types)\n                }\n            }\n\n            val converterVisitor = ConverterVisitor(types, logger).apply {\n                roundEnv.getElementsAnnotatedWith(Converter::class.java).forEach {\n                    val variableElement = it as VariableElement\n                    add(variableElement)\n                    rxHttpWrapper.addConverter(variableElement)\n                }\n            }\n\n            val okClientVisitor = OkClientVisitor(types, logger).apply {\n                roundEnv.getElementsAnnotatedWith(OkClient::class.java).forEach {\n                    val variableElement = it as VariableElement\n                    add(variableElement)\n                    rxHttpWrapper.addOkClient(variableElement)\n                }\n            }\n\n            val domainVisitor = DomainVisitor(types, logger).apply {\n                roundEnv.getElementsAnnotatedWith(Domain::class.java).forEach {\n                    val variableElement = it as VariableElement\n                    add(variableElement)\n                    rxHttpWrapper.addDomain(variableElement)\n                }\n            }\n\n            val defaultDomainVisitor = DefaultDomainVisitor(types, logger).apply {\n                set(roundEnv.getElementsAnnotatedWith(DefaultDomain::class.java))\n            }\n\n            //Generate RxHttp.java\n            RxHttpGenerator().apply {\n                this.paramsVisitor = paramsVisitor\n                this.converterVisitor = converterVisitor\n                this.domainVisitor = domainVisitor\n                this.defaultDomainVisitor = defaultDomainVisitor\n                this.okClientVisitor = okClientVisitor\n            }.generateCode(filer)\n\n            //Generate BaseRxHttp.java\n            BaseRxHttpGenerator(isAndroidPlatform() && androidPlatform, parserVisitor).generateCode(filer)\n\n            // 生成 RxHttp 封装类\n            rxHttpWrapper.generateRxWrapper(filer)\n            processed = true\n        } catch (e: Throwable) {\n            e.printStackTrace()\n            logger.printMessage(Diagnostic.Kind.ERROR, e.message)\n        }\n        return true\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/KspProcessor.kt",
    "content": "package com.rxhttp.compiler\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.processing.SymbolProcessorEnvironment\nimport com.google.devtools.ksp.processing.SymbolProcessorProvider\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSPropertyDeclaration\nimport com.rxhttp.compiler.common.versionCompare\nimport com.rxhttp.compiler.ksp.BaseRxHttpGenerator\nimport com.rxhttp.compiler.ksp.ClassHelper\nimport com.rxhttp.compiler.ksp.ConverterVisitor\nimport com.rxhttp.compiler.ksp.DefaultDomainVisitor\nimport com.rxhttp.compiler.ksp.DomainVisitor\nimport com.rxhttp.compiler.ksp.KClassHelper\nimport com.rxhttp.compiler.ksp.OkClientVisitor\nimport com.rxhttp.compiler.ksp.ParamsVisitor\nimport com.rxhttp.compiler.ksp.ParserVisitor\nimport com.rxhttp.compiler.ksp.RxHttpGenerator\nimport com.rxhttp.compiler.ksp.RxHttpWrapper\nimport rxhttp.wrapper.annotation.Converter\nimport rxhttp.wrapper.annotation.DefaultDomain\nimport rxhttp.wrapper.annotation.Domain\nimport rxhttp.wrapper.annotation.OkClient\nimport rxhttp.wrapper.annotation.Param\nimport rxhttp.wrapper.annotation.Parser\n\n/**\n * User: ljx\n * Date: 2021/10/8\n * Time: 16:31\n */\nclass KspProcessor(private val env: SymbolProcessorEnvironment) : SymbolProcessor {\n\n    private var processed: Boolean = false\n    private var androidPlatform = true\n\n    @KspExperimental\n    override fun process(resolver: Resolver): List<KSAnnotated> {\n        val logger = env.logger\n        val options = env.options\n        val codeGenerator = env.codeGenerator\n        isKps2 = env.kspVersion.toString().versionCompare(\"2.0.0\") >= 0\n\n        val debug = options[rxhttp_debug].toBoolean()\n        if (debug) {\n            logger.warn(\n                \"LJX process getAllFiles.size=${resolver.getAllFiles().toList().size} \" +\n                        \"newFiles.size=${resolver.getNewFiles().toList().size}\"\n            )\n        }\n        androidPlatform = (options[rxhttp_android_platform] ?: \"true\").toBoolean()\n        if (processed) return emptyList()\n        processed = true\n\n        rxHttpPackage = options[rxhttp_package] ?: defaultPackageName\n        initRxJavaVersion(options[rxhttp_rxjava])\n\n        ClassHelper().generatorStaticClass(codeGenerator)\n        KClassHelper(androidPlatform).generatorStaticClass(codeGenerator)\n\n        if (resolver.getAllFiles().toList().isEmpty()) {\n            return emptyList()\n        }\n\n        val rxHttpWrapper = RxHttpWrapper(logger)\n\n        val domainVisitor = DomainVisitor(resolver, logger)\n        resolver.getSymbolsWithAnnotation(Domain::class.java.name).forEach {\n            if (it is KSPropertyDeclaration) {\n                it.accept(domainVisitor, Unit)\n                rxHttpWrapper.addDomain(it)\n            }\n        }\n\n        val defaultDomainVisitor = DefaultDomainVisitor(resolver, logger)\n        resolver.getSymbolsWithAnnotation(DefaultDomain::class.java.name).forEach {\n            if (it is KSPropertyDeclaration) {\n                it.accept(defaultDomainVisitor, Unit)\n            }\n        }\n\n        val okClientVisitor = OkClientVisitor(resolver, logger)\n        resolver.getSymbolsWithAnnotation(OkClient::class.java.name).forEach {\n            if (it is KSPropertyDeclaration) {\n                it.accept(okClientVisitor, Unit)\n                rxHttpWrapper.addOkClient(it)\n            }\n        }\n\n        val converterVisitor = ConverterVisitor(resolver, logger)\n        resolver.getSymbolsWithAnnotation(Converter::class.java.name).forEach {\n            if (it is KSPropertyDeclaration) {\n                it.accept(converterVisitor, Unit)\n                rxHttpWrapper.addConverter(it)\n            }\n        }\n\n        val parserVisitor = ParserVisitor(resolver, logger)\n        resolver.getSymbolsWithAnnotation(Parser::class.java.name).forEach {\n            if (it is KSClassDeclaration) {\n                it.accept(parserVisitor, Unit)\n            }\n        }\n\n        val paramsVisitor = ParamsVisitor(logger, resolver)\n        resolver.getSymbolsWithAnnotation(Param::class.java.name).forEach {\n            if (it is KSClassDeclaration) {\n                it.accept(paramsVisitor, Unit)\n                rxHttpWrapper.add(it)\n            }\n        }\n        rxHttpWrapper.generateRxWrapper(codeGenerator)\n\n        //取第一个源文件做为输出文件的关联文件(如果有使用注解，则忽略该变量)\n        //以修复https://github.com/liujingxing/rxhttp/issues/489提到的bug\n        val defaultKsFile = resolver.getAllFiles().firstOrNull()\n\n        RxHttpGenerator(logger, defaultKsFile).apply {\n            this.paramsVisitor = paramsVisitor\n            this.domainVisitor = domainVisitor\n            this.okClientVisitor = okClientVisitor\n            this.converterVisitor = converterVisitor\n            this.defaultDomainVisitor = defaultDomainVisitor\n        }.generateCode(codeGenerator)\n\n        BaseRxHttpGenerator(logger, androidPlatform, defaultKsFile, parserVisitor)\n            .generateCode(codeGenerator)\n        return emptyList()\n    }\n}\n\nclass KspProvider : SymbolProcessorProvider {\n    override fun create(environment: SymbolProcessorEnvironment) = KspProcessor(environment)\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/RxJavaVersion.kt",
    "content": "package com.rxhttp.compiler\n\nimport com.rxhttp.compiler.common.versionCompare\n\n/**\n * RxJava 版本管理\n * User: ljx\n * Date: 2020/4/12\n * Time: 15:33\n */\nprivate var rxJavaVersion: String? = null\n\nprivate val rxJavaClassList = LinkedHashMap<String, String>()\n\nfun getClassPath(simpleName: String): String = rxJavaClassList[simpleName] + \".$simpleName\"\n\nfun isDependenceRxJava() = rxJavaVersion != null\n\nfun initRxJavaVersion(version: String?) {\n    val realVersion = when {\n        version.equals(\"RxJava2\", true) -> \"2.0.0\"\n        version.equals(\"RxJava3\", true) -> \"3.0.0\"\n        else -> version\n    } ?: return\n    rxJavaVersion = realVersion\n    if (realVersion.versionCompare(\"3.0.0\") >= 0) {\n        rxJavaClassList[\"Scheduler\"] = \"io.reactivex.rxjava3.core\"\n        rxJavaClassList[\"Observable\"] = \"io.reactivex.rxjava3.core\"\n        rxJavaClassList[\"Consumer\"] = \"io.reactivex.rxjava3.functions\"\n        rxJavaClassList[\"Schedulers\"] = \"io.reactivex.rxjava3.schedulers\"\n        rxJavaClassList[\"RxJavaPlugins\"] = \"io.reactivex.rxjava3.plugins\"\n        rxJavaClassList[\"Observer\"] = \"io.reactivex.rxjava3.core\"\n        rxJavaClassList[\"Exceptions\"] = \"io.reactivex.rxjava3.exceptions\"\n        rxJavaClassList[\"Disposable\"] = \"io.reactivex.rxjava3.disposables\"\n        rxJavaClassList[\"DisposableHelper\"] = \"io.reactivex.rxjava3.internal.disposables\"\n        rxJavaClassList[\"Disposable\"] = \"io.reactivex.rxjava3.disposables\"\n        rxJavaClassList[\"ObservableSource\"] = \"io.reactivex.rxjava3.core\"\n        rxJavaClassList[\"TrampolineScheduler\"] = \"io.reactivex.rxjava3.internal.schedulers\"\n        rxJavaClassList[\"AndroidSchedulers\"] = \"io.reactivex.rxjava3.android.schedulers\"\n    } else {\n        rxJavaClassList[\"Scheduler\"] = \"io.reactivex\"\n        rxJavaClassList[\"Observable\"] = \"io.reactivex\"\n        rxJavaClassList[\"Consumer\"] = \"io.reactivex.functions\"\n        rxJavaClassList[\"Schedulers\"] = \"io.reactivex.schedulers\"\n        rxJavaClassList[\"RxJavaPlugins\"] = \"io.reactivex.plugins\"\n        rxJavaClassList[\"Observer\"] = \"io.reactivex\"\n        rxJavaClassList[\"Exceptions\"] = \"io.reactivex.exceptions\"\n        rxJavaClassList[\"Disposable\"] = \"io.reactivex.disposables\"\n        rxJavaClassList[\"DisposableHelper\"] = \"io.reactivex.internal.disposables\"\n        rxJavaClassList[\"Disposable\"] = \"io.reactivex.disposables\"\n        rxJavaClassList[\"ObservableSource\"] = \"io.reactivex\"\n        rxJavaClassList[\"TrampolineScheduler\"] = \"io.reactivex.internal.schedulers\"\n        rxJavaClassList[\"AndroidSchedulers\"] = \"io.reactivex.android.schedulers\"\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/Variables.kt",
    "content": "package com.rxhttp.compiler\n\n/**\n * User: ljx\n * Date: 2021/11/23\n * Time: 17:38\n */\nlateinit var rxHttpPackage: String  //RxHttp相关类的包名\n\nvar isKps2: Boolean = false"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/common/KtUtil.kt",
    "content": "package com.rxhttp.compiler.common\n\nimport com.rxhttp.compiler.K_ARRAY_TYPE\nimport com.rxhttp.compiler.K_TYPE\nimport com.rxhttp.compiler.isDependenceRxJava\nimport com.rxhttp.compiler.ksp.isVararg\nimport com.rxhttp.compiler.ksp.parameterizedBy\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpKClass\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.INT\nimport com.squareup.kotlinpoet.KModifier\nimport com.squareup.kotlinpoet.LIST\nimport com.squareup.kotlinpoet.LambdaTypeName\nimport com.squareup.kotlinpoet.MemberName\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.ParameterizedTypeName\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.TypeVariableName\nimport com.squareup.kotlinpoet.UNIT\nimport java.util.Locale\n\n/**\n * User: ljx\n * Date: 2023/6/23\n * Time: 20:23\n */\n\nfun getRxHttpExtensionFileSpec(\n    toObservableXxxFunList: List<FunSpec>,\n    toAwaitXxxFunList: List<FunSpec>,\n    toFlowXxxFunList: List<FunSpec>,\n): FileSpec {\n    val t = TypeVariableName(\"T\")\n    val listT = LIST.parameterizedBy(t)\n    val reifiedT = t.copy(reified = true)\n    val baseRxHttpName = rxhttpKClass.peerClass(\"BaseRxHttp\")\n    val observableCall = rxhttpKClass.peerClass(\"ObservableCall\")\n    val observableCallT = observableCall.parameterizedBy(\"T\")\n\n    val fileSpecBuilder = FileSpec.builder(rxHttpPackage, \"RxHttpExtension\")\n        .addImport(\"rxhttp.wrapper.utils\", \"javaTypeOf\")\n\n    FunSpec.builder(\"executeList\")\n        .addModifiers(KModifier.INLINE)\n        .receiver(baseRxHttpName)\n        .addTypeVariable(reifiedT)\n        .addStatement(\"return executeClass<List<T>>()\")\n        .returns(listT)\n        .build()\n        .apply { fileSpecBuilder.addFunction(this) }\n\n    FunSpec.builder(\"executeClass\")\n        .addModifiers(KModifier.INLINE)\n        .receiver(baseRxHttpName)\n        .addTypeVariable(reifiedT)\n        .addStatement(\"return executeClass<T>(javaTypeOf<T>())\")\n        .returns(t)\n        .build()\n        .apply { fileSpecBuilder.addFunction(this) }\n\n    if (isDependenceRxJava()) {\n        FunSpec.builder(\"toObservableList\")\n            .addModifiers(KModifier.INLINE)\n            .receiver(baseRxHttpName)\n            .addTypeVariable(reifiedT)\n            .addStatement(\"return toObservable<List<T>>()\")\n            .returns(observableCall.parameterizedBy(\"List<T>\"))\n            .build()\n            .apply { fileSpecBuilder.addFunction(this) }\n\n        FunSpec.builder(\"toObservable\")\n            .addModifiers(KModifier.INLINE)\n            .receiver(baseRxHttpName)\n            .addTypeVariable(reifiedT)\n            .addStatement(\"return toObservable<T>(javaTypeOf<T>())\")\n            .returns(observableCallT)\n            .build()\n            .apply { fileSpecBuilder.addFunction(this) }\n        toObservableXxxFunList.forEach { fileSpecBuilder.addFunction(it) }\n    }\n\n    toAwaitXxxFunList.forEach { fileSpecBuilder.addFunction(it) }\n    toFlowXxxFunList.forEach { fileSpecBuilder.addFunction(it) }\n    return fileSpecBuilder.build()\n}\n\n\n//根据toAwaitXxx方法生成toFlowXxx方法\nfun FunSpec.generateToFlowXxxFun(): List<FunSpec> {\n    val callFactoryName = ClassName(\"rxhttp.wrapper\", \"CallFactory\")\n    val progressName = ClassName(\"rxhttp.wrapper.entity\", \"Progress\")\n    val progressTName = ClassName(\"rxhttp.wrapper.entity\", \"ProgressT\")\n    val progressSuspendLambdaName = LambdaTypeName\n        .get(parameters = arrayOf(progressName), returnType = UNIT).copy(suspending = true)\n    val toFlow = MemberName(\"rxhttp\", \"toFlow\")\n    val toFlowProgress = MemberName(\"rxhttp\", \"toFlowProgress\")\n    val bodyParamFactory = callFactoryName.peerClass(\"BodyParamFactory\")\n    val flow = ClassName(\"kotlinx.coroutines.flow\", \"Flow\")\n\n    val funList = mutableListOf<FunSpec>()\n    val parseName = name.substring(7) // Remove the prefix `toAwait`\n    val typeVariables = typeVariables\n    val paramNames = parameters.toParamNames()\n    val toAwaitXxxReturnType = returnType as ParameterizedTypeName\n    val toAwaitXxxTypeArguments = toAwaitXxxReturnType.typeArguments\n    FunSpec.builder(\"toFlow$parseName\")\n        .addModifiers(modifiers)\n        .receiver(callFactoryName)\n        .addParameters(parameters)\n        .addTypeVariables(typeVariables)\n        .addStatement(\n            \"return %M(toAwait$parseName${typeVariables.getTypeVariableString()}($paramNames))\",\n            toFlow\n        )\n        .returns(flow.parameterizedBy(toAwaitXxxTypeArguments))\n        .build()\n        .apply { funList.add(this) }\n\n    if (typeVariables.isNotEmpty()) {\n        val capacityParam = ParameterSpec.builder(\"capacity\", INT)\n            .defaultValue(\"2\")\n            .build()\n        val isInLine = KModifier.INLINE in modifiers\n        val builder = ParameterSpec.builder(\"progress\", progressSuspendLambdaName)\n        if (isInLine) builder.addModifiers(KModifier.NOINLINE)\n        FunSpec.builder(\"toFlow$parseName\")\n            .addModifiers(modifiers)\n            .receiver(bodyParamFactory)\n            .addTypeVariables(typeVariables)\n            .addParameters(parameters)\n            .addParameter(capacityParam)\n            .addParameter(builder.build())\n            .addStatement(\n                \"return %M(toAwait$parseName${typeVariables.getTypeVariableString()}($paramNames), capacity, progress)\",\n                toFlow\n            )\n            .returns(flow.parameterizedBy(toAwaitXxxTypeArguments))\n            .build()\n            .apply { funList.add(this) }\n\n        FunSpec.builder(\"toFlow${parseName}Progress\")\n            .addModifiers(modifiers)\n            .receiver(bodyParamFactory)\n            .addTypeVariables(typeVariables)\n            .addParameters(parameters)\n            .addParameter(capacityParam)\n            .addStatement(\n                \"return %M(toAwait$parseName${typeVariables.getTypeVariableString()}($paramNames), capacity)\",\n                toFlowProgress\n            )\n            .returns(flow.parameterizedBy(progressTName.parameterizedBy(toAwaitXxxTypeArguments)))\n            .build()\n            .apply { funList.add(this) }\n    }\n    return funList\n}\n\nfun List<ParameterSpec>.flapTypeParameterSpecTypes(\n    typeVariableNames: List<TypeVariableName>\n): List<ParameterSpec> {\n    val parameterSpecs = mutableListOf<ParameterSpec>()\n    forEachIndexed { index, parameterSpec ->\n        if (index == 0 && typeVariableNames.isNotEmpty() &&\n            (parameterSpec.isArrayType() || parameterSpec.isVarargType())\n        ) {\n            typeVariableNames.mapTo(parameterSpecs) {\n                val variableName = \"${it.name.lowercase(Locale.getDefault())}Type\"\n                ParameterSpec.builder(variableName, K_TYPE).build()\n            }\n        } else {\n            parameterSpecs.add(parameterSpec)\n        }\n    }\n    return parameterSpecs\n}\n\nfun ParameterSpec.isArrayType() = type == K_ARRAY_TYPE\nfun ParameterSpec.isVarargType() = isVararg() && type == K_TYPE"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/common/ObservableUtil.kt",
    "content": "package com.rxhttp.compiler.common\n\nimport com.rxhttp.compiler.getClassPath\nimport com.rxhttp.compiler.rxHttpPackage\n\n/**\n * User: ljx\n * Date: 2022/9/22\n * Time: 14:59\n */\nfun getObservableClass(): Map<String, String> {\n    val map = mutableMapOf<String, String>()\n    map[\"ObservableCall\"] = \"\"\"\n        package $rxHttpPackage;\n\n        import org.jetbrains.annotations.NotNull;\n        import org.jetbrains.annotations.Nullable;\n\n        import java.io.IOException;\n        import java.util.Objects;\n        import java.util.concurrent.atomic.AtomicReference;\n\n        import ${getClassPath(\"AndroidSchedulers\")};\n        import ${getClassPath(\"Observable\")};\n        import ${getClassPath(\"Observer\")};\n        import ${getClassPath(\"Scheduler\")};\n        import ${getClassPath(\"Disposable\")};\n        import ${getClassPath(\"Exceptions\")};\n        import ${getClassPath(\"Consumer\")};\n        import ${getClassPath(\"DisposableHelper\")};\n        import ${getClassPath(\"RxJavaPlugins\")};\n        import ${getClassPath(\"Schedulers\")};\n        import okhttp3.Call;\n        import okhttp3.Callback;\n        import okhttp3.Response;\n        import rxhttp.wrapper.BodyParamFactory;\n        import rxhttp.wrapper.CallFactory;\n        import rxhttp.wrapper.callback.ProgressCallback;\n        import rxhttp.wrapper.entity.OkResponse;\n        import rxhttp.wrapper.entity.Progress;\n        import rxhttp.wrapper.exception.ProxyException;\n        import rxhttp.wrapper.parse.OkResponseParser;\n        import rxhttp.wrapper.parse.Parser;\n        import rxhttp.wrapper.parse.StreamParser;\n        import rxhttp.wrapper.utils.LogUtil;\n        \n        /**\n         * User: ljx\n         * Date: 2020/9/5\n         * Time: 21:59\n         */\n        public final class ObservableCall<T> extends Observable<T> {\n\n            private final Parser<T> parser;\n            private final CallFactory callFactory;\n            private boolean syncRequest = false;\n            //上传/下载进度回调最小周期, 值越小，回调事件越多，设置一个合理值，可避免密集回调\n            private int minPeriod = Integer.MIN_VALUE;\n\n            ObservableCall(CallFactory callFactory, Parser<T> parser) {\n                this.callFactory = callFactory;\n                this.parser = parser;\n            }\n\n            @Override\n            protected void subscribeActual(Observer<? super T> observer) {\n                CallExecuteDisposable<T> d = syncRequest ? new CallExecuteDisposable<>(observer, callFactory, parser) :\n                    new CallEnqueueDisposable<>(observer, callFactory, parser);\n                observer.onSubscribe(d);\n                if (d.isDisposed()) {\n                    return;\n                }\n                if (minPeriod != Integer.MIN_VALUE && observer instanceof ProgressCallback) {\n                    ProgressCallback pc = (ProgressCallback) observer;\n                    Parser<?> parser = this.parser;\n                    while (parser instanceof OkResponseParser<?>) {\n                        parser = ((OkResponseParser<?>) parser).parser;\n                    }\n                    if (parser instanceof StreamParser) {\n                         ((StreamParser<?>) parser).setProgressCallback(minPeriod, pc);\n                    } else if (callFactory instanceof BodyParamFactory) {\n                        ((BodyParamFactory) callFactory).getParam().setProgressCallback(minPeriod, pc);\n                    }\n                }\n                d.run();\n            }\n            \n            @NotNull\n            public ObservableCall<@NotNull OkResponse<@Nullable T>> toObservableOkResponse() { \n                return new ObservableCall<>(callFactory, new OkResponseParser<>(parser));\n            }\n\n            @NotNull \n            public ObservableCall<@NotNull T> syncRequest() {\n                syncRequest = true;\n                return this;\n            }\n\n            @NotNull\n            public Observable<@NotNull T> onProgress(@NotNull Consumer<Progress<@Nullable T>> progressConsumer) {\n                return onProgress(Schedulers.io(), progressConsumer);\n            }\n\n            @NotNull\n            public Observable<@NotNull T> onProgress(@NotNull Scheduler scheduler, @NotNull Consumer<Progress<@Nullable T>> progressConsumer) {\n                return onProgress(2, scheduler, progressConsumer);\n            }\n\n            @NotNull\n            public Observable<@NotNull T> onMainProgress(@NotNull Consumer<Progress<@Nullable T>> progressConsumer) {\n                return onMainProgress(2, progressConsumer);\n            }\n\n            @NotNull\n            public Observable<@NotNull T> onMainProgress(int capacity, @NotNull Consumer<Progress<@Nullable T>> progressConsumer) {\n                return onProgress(capacity, AndroidSchedulers.mainThread(), progressConsumer);\n            }\n            \n            @NotNull\n            public Observable<@NotNull T> onMainProgress(int capacity, int minPeriod, @NotNull Consumer<Progress<@Nullable T>> progressConsumer) {\n                return onProgress(capacity, minPeriod, AndroidSchedulers.mainThread(), progressConsumer);\n            }\n\n            @NotNull\n            public Observable<@NotNull T> onProgress(int capacity, @NotNull Scheduler scheduler, @NotNull Consumer<Progress<@Nullable T>> progressConsumer) {\n                return onProgress(capacity, 500, scheduler, progressConsumer);\n            }\n\n            /**\n             * Upload or Download progress callback\n             *\n             * @param capacity         queue size, must be in [2..100], is invalid when the scheduler is TrampolineScheduler\n             * @param minPeriod        minimum period of progress callback, must be between 1 and {@link Integer.MAX_VALUE}, The default value is 500 milliseconds,\n             * @param scheduler        the Scheduler to notify Observers on\n             * @param progressConsumer progress callback\n             * @return the new Observable instance\n             */\n            @NotNull \n            public Observable<@NotNull T> onProgress(int capacity, int minPeriod, @NotNull Scheduler scheduler, @NotNull Consumer<Progress<@Nullable T>> progressConsumer) {\n                if (capacity < 2 || capacity > 100) {\n                    throw new IllegalArgumentException(\"capacity must be in [2..100], but it was \" + capacity);\n                }\n                if (minPeriod < 0) {\n                    throw new IllegalArgumentException(\"minPeriod must be between 0 and Integer.MAX_VALUE, but it was \" + minPeriod);\n                }\n                Objects.requireNonNull(scheduler, \"scheduler is null\");\n                Parser<?> streamParser = parser;\n                while (streamParser instanceof OkResponseParser<?>) {\n                    streamParser = ((OkResponseParser<?>) streamParser).parser;\n                }\n                if (!(streamParser instanceof StreamParser) && !(callFactory instanceof BodyParamFactory)) {\n                    throw new UnsupportedOperationException(\"parser is \" + streamParser.getClass().getName() + \", callFactory is \" + callFactory.getClass().getName());\n                }\n                this.minPeriod = minPeriod;\n                return new ObservableProgress<>(this, capacity, scheduler, progressConsumer);\n            }\n\n            private static class CallEnqueueDisposable<T> extends CallExecuteDisposable<T> implements Callback {\n\n                CallEnqueueDisposable(Observer<? super T> downstream, CallFactory callFactory, Parser<T> parser) {\n                    super(downstream, callFactory, parser);\n                }\n\n                @Override\n                public void onResponse(@NotNull Call call, @NotNull Response response) {\n                    try {\n                        T t = Objects.requireNonNull(parser.onParse(response), \"The onParse function returned a null value.\");\n                        if (!disposed) {\n                            downstream.onNext(t);\n                        }\n                        if (!disposed) {\n                            downstream.onComplete();\n                        }\n                    } catch (Throwable t) {\n                        onError(call, t);\n                    }\n                }\n\n                @Override\n                public void onFailure(@NotNull Call call, @NotNull IOException e) {\n                    onError(call, e);\n                }\n\n                @Override\n                public void run() {\n                    try {\n                        call = callFactory.newCall();\n                        call.enqueue(this);\n                    } catch (Throwable e) {\n                        onError(call, e);\n                    }\n                }\n            }\n\n\n            private static class CallExecuteDisposable<T> implements Disposable {\n\n                protected final Observer<? super T> downstream;\n                protected final Parser<T> parser;\n                protected final CallFactory callFactory;\n                protected volatile boolean disposed;\n                protected Call call;\n                private final AtomicReference<Disposable> upstream;\n\n                CallExecuteDisposable(Observer<? super T> downstream, CallFactory callFactory, Parser<T> parser) {\n                    this.downstream = downstream;\n                    this.callFactory = callFactory;\n                    this.parser = parser;\n                    upstream = new AtomicReference<>();\n                }\n\n                public void run() {\n                    try {\n                        call = callFactory.newCall();\n                        Response response = call.execute();\n                        T t = Objects.requireNonNull(parser.onParse(response), \"The onParse function returned a null value.\");\n                        if (!disposed) {\n                            downstream.onNext(t);\n                        }\n                        if (!disposed) {\n                            downstream.onComplete();\n                        }\n                    } catch (Throwable e) {\n                        onError(call, e);\n                    }\n                }\n\n                void onError(@Nullable Call call, Throwable e) {\n                    LogUtil.logCall(call, e);\n                    Exceptions.throwIfFatal(e);\n                    if (!disposed) {\n                        downstream.onError(e);\n                    } else {\n                        RxJavaPlugins.onError(e);\n                    }\n                }\n\n                @Override\n                public void dispose() {\n                    DisposableHelper.dispose(upstream);\n                    disposed = true;\n                    if (call != null)\n                        call.cancel();\n                }\n\n                @Override\n                public boolean isDisposed() {\n                    return disposed;\n                }\n\n                public void setDisposable(Disposable d) {\n                    DisposableHelper.setOnce(upstream, d);\n                }\n            }\n        }\n\n    \"\"\".trimIndent()\n\n    map[\"ObservableProgress\"] =\"\"\"\n        package $rxHttpPackage;\n        \n        import org.jetbrains.annotations.NotNull;\n        import org.jetbrains.annotations.Nullable;\n\n        import java.util.Queue;\n        import java.util.concurrent.LinkedBlockingQueue;\n        import java.util.concurrent.atomic.AtomicInteger;\n\n        import ${getClassPath(\"Observable\")};\n        import ${getClassPath(\"Observer\")};\n        import ${getClassPath(\"Scheduler\")};\n        import ${getClassPath(\"Scheduler\")}.Worker;\n        import ${getClassPath(\"Disposable\")};\n        import ${getClassPath(\"Exceptions\")};\n        import ${getClassPath(\"Consumer\")};\n        import ${getClassPath(\"DisposableHelper\")};\n        import ${getClassPath(\"TrampolineScheduler\")};\n        import ${getClassPath(\"RxJavaPlugins\")};\n        import rxhttp.wrapper.callback.ProgressCallback;\n        import rxhttp.wrapper.entity.Progress;\n\n        public final class ObservableProgress<T> extends Observable<T> {\n\n            private final Observable<T> source;\n            private final int capacity;\n            private final Scheduler scheduler;\n            private final Consumer<Progress<@Nullable T>> progressConsumer;\n\n            ObservableProgress(Observable<T> source, int capacity, Scheduler scheduler, Consumer<Progress<@Nullable T>> progressConsumer) {\n                this.source = source;\n                this.capacity = capacity;\n                this.scheduler = scheduler;\n                this.progressConsumer = progressConsumer;\n            }\n\n            @Override\n            protected void subscribeActual(@NotNull Observer<? super T> observer) {\n                if (scheduler instanceof TrampolineScheduler) {\n                    source.subscribe(new SyncObserver<>(observer, progressConsumer));\n                } else {\n                    Worker worker = scheduler.createWorker();\n                    source.subscribe(new AsyncObserver<>(worker, observer, capacity, progressConsumer));\n                }\n            }\n\n            private static final class SyncObserver<T> implements Observer<T>, Disposable, ProgressCallback {\n\n                private final Observer<? super T> downstream;\n                private final Consumer<Progress<@Nullable T>> progressConsumer;\n                private Disposable upstream;\n                private boolean done;\n\n                SyncObserver(Observer<? super T> actual, Consumer<Progress<@Nullable T>> progressConsumer) {\n                    this.downstream = actual;\n                    this.progressConsumer = progressConsumer;\n                }\n\n                @Override\n                public void onSubscribe(@NotNull Disposable d) {\n                    if (DisposableHelper.validate(this.upstream, d)) {\n                        this.upstream = d;\n                        downstream.onSubscribe(this);\n                    }\n                }\n\n                // upload/download progress callback\n                @Override\n                public void onProgress(long currentSize, long totalSize, long speed) {\n                    if (done) {\n                        return;\n                    }\n                    try {\n                        progressConsumer.accept(new Progress<>(currentSize, totalSize, speed));\n                    } catch (Throwable t) {\n                        fail(t);\n                    }\n                }\n\n                @Override\n                public void onNext(@NotNull T t) {\n                    if (done) {\n                        return;\n                    }\n                    downstream.onNext(t);\n                }\n\n                @Override\n                public void onError(@NotNull Throwable t) {\n                    if (done) {\n                        RxJavaPlugins.onError(t);\n                        return;\n                    }\n                    done = true;\n                    downstream.onError(t);\n                }\n\n                @Override\n                public void onComplete() {\n                    if (done) {\n                        return;\n                    }\n                    done = true;\n                    downstream.onComplete();\n                }\n\n                @Override\n                public void dispose() {\n                    upstream.dispose();\n                }\n\n                @Override\n                public boolean isDisposed() {\n                    return upstream.isDisposed();\n                }\n\n                private void fail(Throwable t) {\n                    Exceptions.throwIfFatal(t);\n                    upstream.dispose();\n                    onError(t);\n                }\n            }\n\n\n            private static final class AsyncObserver<T> extends AtomicInteger implements Observer<T>,\n                Disposable, ProgressCallback, Runnable {\n\n                private final Observer<? super T> downstream;\n                private final Queue<Object> queue;\n                private final Scheduler.Worker worker;\n                private final Consumer<Progress<@Nullable T>> progressConsumer;\n                private Disposable upstream;\n                private Throwable error;\n                private volatile boolean done;\n                private volatile boolean disposed;\n\n                AsyncObserver(Scheduler.Worker worker, Observer<? super T> actual, int capacity, Consumer<Progress<@Nullable T>> progressConsumer) {\n                    this.downstream = actual;\n                    this.worker = worker;\n                    this.progressConsumer = progressConsumer;\n                    queue = new LinkedBlockingQueue<>(capacity);\n                }\n\n                @Override\n                public void onSubscribe(@NotNull Disposable d) {\n                    if (DisposableHelper.validate(this.upstream, d)) {\n                        this.upstream = d;\n                        downstream.onSubscribe(this);\n                    }\n                }\n\n                // upload/download progress callback\n                @Override\n                public void onProgress(long currentSize, long totalSize, long speed) {\n                    if (done) {\n                        return;\n                    }\n                    offer(new Progress<>(currentSize, totalSize, speed));\n                }\n\n                @Override\n                public void onNext(@NotNull T t) {\n                    if (done) {\n                        return;\n                    }\n                    offer(t);\n                }\n\n                private void offer(Object o) {\n                    while (!queue.offer(o)) {\n                        queue.poll();\n                    }\n                    schedule();\n                }\n\n                @Override\n                public void onError(@NotNull Throwable t) {\n                    if (done) {\n                        RxJavaPlugins.onError(t);\n                        return;\n                    }\n                    error = t;\n                    done = true;\n                    schedule();\n                }\n\n                @Override\n                public void onComplete() {\n                    if (done) {\n                        return;\n                    }\n                    done = true;\n                    schedule();\n                }\n\n\n                void schedule() {\n                    if (getAndIncrement() == 0) {\n                        worker.schedule(this);\n                    }\n                }\n\n                @SuppressWarnings(\"unchecked\")\n                @Override\n                public void run() {\n                    int missed = 1;\n\n                    final Queue<?> q = queue;\n                    final Observer<? super T> a = downstream;\n                    while (!checkTerminated(done, q.isEmpty(), a)) {\n                        for (; ; ) {\n                            boolean d = done;\n                            Object o;\n                            try {\n                                o = q.poll();\n\n                                boolean empty = o == null;\n\n                                if (checkTerminated(d, empty, a)) {\n                                    return;\n                                }\n                                if (empty) {\n                                    break;\n                                }\n                                if (o instanceof Progress) {\n                                    progressConsumer.accept((Progress<T>) o);\n                                } else {\n                                    a.onNext((T) o);\n                                }\n                            } catch (Throwable ex) {\n                                Exceptions.throwIfFatal(ex);\n                                disposed = true;\n                                upstream.dispose();\n                                q.clear();\n                                a.onError(ex);\n                                worker.dispose();\n                                return;\n                            }\n                        }\n                        missed = addAndGet(-missed);\n                        if (missed == 0) {\n                            break;\n                        }\n                    }\n                }\n\n                boolean checkTerminated(boolean d, boolean empty, Observer<? super T> a) {\n                    if (isDisposed()) {\n                        queue.clear();\n                        return true;\n                    }\n                    if (d) {\n                        Throwable e = error;\n                        if (e != null) {\n                            disposed = true;\n                            queue.clear();\n                            a.onError(e);\n                            worker.dispose();\n                            return true;\n                        } else if (empty) {\n                            disposed = true;\n                            a.onComplete();\n                            worker.dispose();\n                            return true;\n                        }\n                    }\n                    return false;\n                }\n\n                @Override\n                public void dispose() {\n                    if (!disposed) {\n                        disposed = true;\n                        upstream.dispose();\n                        worker.dispose();\n                        if (getAndIncrement() == 0) {\n                            queue.clear();\n                        }\n                    }\n                }\n\n                @Override\n                public boolean isDisposed() {\n                    return disposed;\n                }\n            }\n        }\n\n    \"\"\".trimIndent()\n    return map;\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/common/StringUtil.kt",
    "content": "package com.rxhttp.compiler.common\n\nimport com.rxhttp.compiler.ksp.isVararg\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.TypeVariableName\n\n/**\n * User: ljx\n * Date: 2022/8/10\n * Time: 11:28\n */\n//返回参数名列表, 多个参数用逗号隔开, 如： a, b, c\nfun List<ParameterSpec>.toParamNames(\n    prefix: CharSequence = \"\",\n    postfix: CharSequence = \"\"\n): String = joinToString(\", \", prefix, postfix) {\n    if (it.isVararg()) \"*${it.name}\" else it.name\n}\n\n//获取泛型字符串 比如:<T> 、<K, V>等等\nfun List<TypeVariableName>.getTypeVariableString(): String {\n    val types = joinToString { it.name }\n    return if (types.isEmpty()) \"\" else \"<$types>\"\n}\n\n//返回 javaTypeOf<T>, javaTypeOf<K>等\nfun List<TypeVariableName>.getTypeOfString(): String =\n    joinToString { \"javaTypeOf<${it.name}>()\" }\n\n\nfun <T> Iterable<T>.joinToStringIndexed(\n    separator: CharSequence = \", \",\n    prefix: CharSequence = \"\",\n    postfix: CharSequence = \"\",\n    limit: Int = -1,\n    truncated: CharSequence = \"...\",\n    transform: ((Int, T) -> CharSequence)\n): String {\n    var index = 0\n    return joinToString(separator, prefix, postfix, limit, truncated) {\n        transform.invoke(index++, it)\n    }\n}\n\nfun String.versionCompare(version: String): Int {\n    val versionArr1 = split(\".\")\n    val versionArr2 = version.split(\".\")\n    val minLen = versionArr1.size.coerceAtMost(versionArr2.size)\n    var diff = 0\n    for (i in 0 until minLen) {\n        val v1 = versionArr1[i]\n        val v2 = versionArr2[i]\n        diff = v1.length - v2.length\n        if (diff == 0) {\n            diff = v1.compareTo(v2)\n        }\n        if (diff != 0) {\n            break\n        }\n    }\n    return if (diff != 0) diff else versionArr1.size - versionArr2.size\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/BaseRxHttpGenerator.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.rxhttp.compiler.getClassPath\nimport com.rxhttp.compiler.isDependenceRxJava\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpClass\nimport com.squareup.javapoet.ClassName\nimport com.squareup.javapoet.CodeBlock\nimport com.squareup.javapoet.JavaFile\nimport com.squareup.javapoet.MethodSpec\nimport com.squareup.javapoet.TypeSpec\nimport com.squareup.javapoet.TypeVariableName\nimport java.io.IOException\nimport javax.annotation.processing.Filer\nimport javax.lang.model.element.Modifier\n\nclass BaseRxHttpGenerator(\n    private val isAndroidPlatform: Boolean,\n    private val parserVisitor: ParserVisitor\n) {\n\n    //生成BaseRxHttp类\n    @Throws(IOException::class)\n    fun generateCode(filer: Filer) {\n        val t = TypeVariableName.get(\"T\")\n        val className = ClassName.get(Class::class.java)\n        val classTName = className.parameterizedBy(t)\n        val list = ClassName.get(List::class.java)\n        val listTName = list.parameterizedBy(t)\n\n        val parser = ClassName.get(\"rxhttp.wrapper.parse\", \"Parser\")\n        val parserT = parser.parameterizedBy(t)\n        val smartParser = parser.peerClass(\"SmartParser\")\n        val streamParser = parser.peerClass(\"StreamParser\")\n\n        val rxJavaPlugins = ClassName.bestGuess(getClassPath(\"RxJavaPlugins\"))\n        val logUtilName = ClassName.get(\"rxhttp.wrapper.utils\", \"LogUtil\")\n        val consumer = ClassName.bestGuess(getClassPath(\"Consumer\"))\n\n        val responseName = ClassName.get(\"okhttp3\", \"Response\")\n        val type = ClassName.get(\"java.lang.reflect\", \"Type\")\n        val parameterizedType = ClassName.get(\"rxhttp.wrapper.entity\", \"ParameterizedTypeImpl\")\n        val outputStreamFactory = ClassName.get(\"rxhttp.wrapper.callback\", \"OutputStreamFactory\")\n        val fileOutputStreamFactory = outputStreamFactory.peerClass(\"FileOutputStreamFactory\")\n        val uriOutputStreamFactory = outputStreamFactory.peerClass(\"UriOutputStreamFactory\")\n        val observableCall = rxhttpClass.peerClass(\"ObservableCall\")\n        val observableCallT = observableCall.parameterizedBy(t)\n\n        val methodList = ArrayList<MethodSpec>() //方法集合\n\n        if (isDependenceRxJava()) {\n            MethodSpec.methodBuilder(\"toObservable\")\n                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                .addTypeVariable(t)\n                .addParameter(parserT, \"parser\")\n                .addStatement(\"return new ObservableCall(this, parser)\")\n                .returns(observableCallT)\n                .build()\n                .apply { methodList.add(this) }\n\n            MethodSpec.methodBuilder(\"toObservable\")\n                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                .addTypeVariable(t)\n                .addParameter(type, \"type\")\n                .addStatement(\"return toObservable(\\$T.wrap(type))\", smartParser)\n                .returns(observableCallT)\n                .build()\n                .apply { methodList.add(this) }\n\n            MethodSpec.methodBuilder(\"toObservable\")\n                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                .addTypeVariable(t)\n                .addParameter(classTName, \"clazz\")\n                .addStatement(\"return toObservable((Type) clazz)\")\n                .returns(observableCallT)\n                .build()\n                .apply { methodList.add(this) }\n\n            MethodSpec.methodBuilder(\"toObservableString\")\n                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                .addStatement(\"return toObservable(String.class)\")\n                .returns(observableCall.parameterizedBy(STRING))\n                .build()\n                .apply { methodList.add(this) }\n\n            MethodSpec.methodBuilder(\"toObservableList\")\n                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                .addTypeVariable(t)\n                .addParameter(classTName, \"clazz\")\n                .addCode(\n                    \"\"\"\n                Type typeList = ParameterizedTypeImpl.get(List.class, clazz);\n                return toObservable(typeList);\n            \"\"\".trimIndent()\n                )\n                .returns(observableCall.parameterizedBy(listTName))\n                .build()\n                .apply { methodList.add(this) }\n\n            MethodSpec.methodBuilder(\"toDownloadObservable\")\n                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                .addParameter(STRING, \"destPath\")\n                .addStatement(\"return toDownloadObservable(destPath, false)\")\n                .returns(observableCall.parameterizedBy(STRING))\n                .build()\n                .apply { methodList.add(this) }\n\n            MethodSpec.methodBuilder(\"toDownloadObservable\")\n                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                .addParameter(STRING, \"destPath\")\n                .addParameter(Boolean::class.java, \"append\")\n                .addStatement(\n                    \"return toDownloadObservable(new \\$T(destPath), append)\",\n                    fileOutputStreamFactory\n                )\n                .returns(observableCall.parameterizedBy(STRING))\n                .build()\n                .apply { methodList.add(this) }\n\n            if (isAndroidPlatform) {\n                val context = ClassName.get(\"android.content\", \"Context\")\n                val uri = ClassName.get(\"android.net\", \"Uri\")\n\n                MethodSpec.methodBuilder(\"toDownloadObservable\")\n                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                    .addParameter(context, \"context\")\n                    .addParameter(uri, \"uri\")\n                    .addStatement(\"return toDownloadObservable(context, uri, false)\")\n                    .returns(observableCall.parameterizedBy(uri))\n                    .build()\n                    .apply { methodList.add(this) }\n\n                MethodSpec.methodBuilder(\"toDownloadObservable\")\n                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                    .addParameter(context, \"context\")\n                    .addParameter(uri, \"uri\")\n                    .addParameter(Boolean::class.java, \"append\")\n                    .addStatement(\n                        \"return toDownloadObservable(new \\$T(context, uri), append)\",\n                        uriOutputStreamFactory\n                    )\n                    .returns(observableCall.parameterizedBy( uri))\n                    .build()\n                    .apply { methodList.add(this) }\n            }\n\n            MethodSpec.methodBuilder(\"toDownloadObservable\")\n                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                .addTypeVariable(t)\n                .addParameter(outputStreamFactory.parameterizedBy( t), \"osFactory\")\n                .addStatement(\"return toDownloadObservable(osFactory, false)\")\n                .returns(observableCallT)\n                .build()\n                .apply { methodList.add(this) }\n\n            MethodSpec.methodBuilder(\"toDownloadObservable\")\n                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n                .addTypeVariable(t)\n                .addParameter(outputStreamFactory.parameterizedBy(t), \"osFactory\")\n                .addParameter(Boolean::class.java, \"append\")\n                .addCode(\n                    $$\"\"\"\n                if (append) {\n                    tag(OutputStreamFactory.class, osFactory);\n                }        \n                return toObservable(new $T<>(osFactory));\n            \"\"\".trimIndent(), streamParser\n                )\n                .returns(observableCallT)\n                .build()\n                .apply { methodList.add(this) }\n        }\n\n        methodList.addAll(parserVisitor.getMethodList(filer))\n\n        MethodSpec.methodBuilder(\"execute\")\n            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n            .addException(IOException::class.java)\n            .addStatement(\"return newCall().execute()\")\n            .returns(responseName)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"execute\")\n            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n            .addTypeVariable(t)\n            .addException(IOException::class.java)\n            .addParameter(parserT, \"parser\")\n            .addStatement(\"return parser.onParse(execute())\")\n            .returns(t)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"executeClass\")\n            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n            .addTypeVariable(t)\n            .addException(IOException::class.java)\n            .addParameter(type, \"type\")\n            .addStatement(\"return execute(\\$T.wrap(type))\", smartParser)\n            .returns(t)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"executeClass\")\n            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n            .addTypeVariable(t)\n            .addException(IOException::class.java)\n            .addParameter(classTName, \"clazz\")\n            .addStatement(\"return executeClass((Type) clazz)\")\n            .returns(t)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"executeString\")\n            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n            .addException(IOException::class.java)\n            .addStatement(\"return executeClass(String.class)\")\n            .returns(STRING)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"executeList\")\n            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n            .addTypeVariable(t)\n            .addException(IOException::class.java)\n            .addParameter(classTName, \"clazz\")\n            .addStatement(\"\\$T typeList = \\$T.get(List.class, clazz)\", type, parameterizedType)\n            .addStatement(\"return executeClass(typeList)\")\n            .returns(listTName)\n            .build()\n            .apply { methodList.add(this) }\n\n        val fileName = \"BaseRxHttp\"\n        val typeSpecBuilder = TypeSpec.classBuilder(fileName)\n            .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)\n            .addSuperinterface(ClassName.get(\"rxhttp.wrapper\", \"ITag\"))\n            .addSuperinterface(ClassName.get(\"rxhttp.wrapper\", \"CallFactory\"))\n            .addMethods(methodList)\n            .addJavadoc(\n                \"\"\"\n                User: ljx\n                Date: 2020/4/11\n                Time: 18:15\n            \"\"\".trimIndent()\n            )\n\n        if (isDependenceRxJava()) {\n            val codeBlock = CodeBlock.of(\n                $$\"\"\"\n                $T<? super Throwable> errorHandler = $T.getErrorHandler();\n                if (errorHandler == null) {                                                \n                    /*                                                                     \n                    RxJava的一个重要的设计理念是：不吃掉任何一个异常, 即抛出的异常无人处理，便会导致程序崩溃                      \n                    这就会导致一个问题，当RxJava“downStream”取消订阅后，“upStream”仍有可能抛出异常，                \n                    这时由于已经取消订阅，“downStream”无法处理异常，此时的异常无人处理，便会导致程序崩溃                       \n                    */                                                                     \n                    RxJavaPlugins.setErrorHandler($T::logRxJavaError);                           \n                }\n                \n            \"\"\".trimIndent(), consumer, rxJavaPlugins, logUtilName\n            )\n            typeSpecBuilder.addStaticBlock(codeBlock)\n        }\n\n        // Write file\n        JavaFile.builder(rxHttpPackage, typeSpecBuilder.build())\n            .indent(\"    \")\n            .skipJavaLangImports(true)\n            .build().writeTo(filer)\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/ClassHelper.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.rxhttp.compiler.common.getObservableClass\nimport com.rxhttp.compiler.isDependenceRxJava\nimport com.rxhttp.compiler.rxHttpPackage\nimport java.io.BufferedWriter\nimport javax.annotation.processing.Filer\n\n\n/**\n * User: ljx\n * Date: 2020/3/31\n * Time: 23:36\n */\nclass ClassHelper(private val isAndroidPlatform: Boolean) {\n\n    private fun isAndroid(s: String) = if (isAndroidPlatform) s else \"\"\n\n    fun generatorStaticClass(filer: Filer) {\n        generatorRxHttpAbstractBodyParam(filer)\n        generatorRxHttpBodyParam(filer)\n        generatorRxHttpFormParam(filer)\n        generatorRxHttpNoBodyParam(filer)\n        generatorRxHttpJsonParam(filer)\n        generatorRxHttpJsonArrayParam(filer)\n        if (isDependenceRxJava()) {\n            getObservableClass().forEach { (t, u) ->\n                generatorClass(filer,t,u)\n            }\n        }\n    }\n\n    private fun generatorRxHttpAbstractBodyParam(filer: Filer) {\n        generatorClass(\n            filer, \"RxHttpAbstractBodyParam\", \"\"\"\n                package $rxHttpPackage;\n                \n                import rxhttp.wrapper.BodyParamFactory;\n                import rxhttp.wrapper.param.AbstractBodyParam;\n\n                /**\n                 * Github\n                 * https://github.com/liujingxing/rxhttp\n                 * https://github.com/liujingxing/rxlife\n                 * https://github.com/liujingxing/rxhttp/wiki/FAQ\n                 * https://github.com/liujingxing/rxhttp/wiki/更新日志\n                 */\n                public class RxHttpAbstractBodyParam<P extends AbstractBodyParam<P>, R extends RxHttpAbstractBodyParam<P, R>> \n                    extends RxHttp<P, R> implements BodyParamFactory {\n\n                    RxHttpAbstractBodyParam(P param) {\n                        super(param);\n                    }\n                }\n            \"\"\".trimIndent()\n        )\n    }\n\n    private fun generatorRxHttpNoBodyParam(filer: Filer) {\n        generatorClass(filer, \"RxHttpNoBodyParam\", \"\"\"\n            package $rxHttpPackage;\n            \n            import org.jetbrains.annotations.NotNull;\n\n            import java.util.Map;\n            \n            import rxhttp.wrapper.param.NoBodyParam;\n\n            /**\n             * Github\n             * https://github.com/liujingxing/rxhttp\n             * https://github.com/liujingxing/rxlife\n             * https://github.com/liujingxing/rxhttp/wiki/FAQ\n             * https://github.com/liujingxing/rxhttp/wiki/更新日志\n             */\n            public class RxHttpNoBodyParam extends RxHttp<NoBodyParam, RxHttpNoBodyParam> {\n                RxHttpNoBodyParam(NoBodyParam param) {\n                    super(param);\n                }\n                \n                public RxHttpNoBodyParam add(String key, Object value) {\n                    return addQuery(key, value);\n                }\n                \n                public RxHttpNoBodyParam add(String key, Object value, boolean add) {\n                    if (add) addQuery(key, value);\n                    return this;\n                }\n                \n                public RxHttpNoBodyParam addAll(Map<String, ?> map) {\n                    return addAllQuery(map);\n                }\n\n                public RxHttpNoBodyParam addEncoded(String key, Object value) {\n                    return addEncodedQuery(key, value);\n                }\n                \n                public RxHttpNoBodyParam addAllEncoded(@NotNull Map<String, ?> map) {\n                    return addAllEncodedQuery(map);\n                }\n            }\n\n        \"\"\".trimIndent())\n    }\n\n    private fun generatorRxHttpBodyParam(filer: Filer) {\n        generatorClass(\n            filer, \"RxHttpBodyParam\", \"\"\"\n            package $rxHttpPackage;\n            ${isAndroid(\"\"\"\n            import android.content.Context;\n            import android.net.Uri;\n            import rxhttp.wrapper.entity.UriRequestBody;\n            \"\"\")}\n            import org.jetbrains.annotations.Nullable;\n            \n            import java.io.File;\n            \n            import okhttp3.MediaType;\n            import okhttp3.RequestBody;\n            import okio.ByteString;\n            import rxhttp.wrapper.param.BodyParam;\n            import rxhttp.wrapper.OkHttpCompat;\n            import rxhttp.wrapper.entity.FileRequestBody;\n            import rxhttp.wrapper.utils.BuildUtil;\n\n            /**\n             * Github\n             * https://github.com/liujingxing/rxhttp\n             * https://github.com/liujingxing/rxlife\n             */\n            public class RxHttpBodyParam extends RxHttpAbstractBodyParam<BodyParam, RxHttpBodyParam> {\n                RxHttpBodyParam(BodyParam param) {\n                    super(param);\n                }\n                \n                public RxHttpBodyParam setBody(String content, @Nullable MediaType contentType) {\n                    return setBody(OkHttpCompat.create(contentType, content));\n                }\n                \n                public RxHttpBodyParam setBody(ByteString content, @Nullable MediaType contentType) {\n                    return setBody(OkHttpCompat.create(contentType, content));\n                }\n                \n                public RxHttpBodyParam setBody(byte[] content, @Nullable MediaType mediaType) {\n                    return setBody(content, mediaType, 0, content.length);\n                }\n                \n                public RxHttpBodyParam setBody(byte[] content, @Nullable MediaType mediaType, int offset, int byteCount) {\n                    return setBody(OkHttpCompat.create(mediaType, content, offset, byteCount));\n                }\n                \n                public RxHttpBodyParam setBody(File file) {\n                    return setBody(file, BuildUtil.getMediaType(file.getName()));\n                }\n                \n                public RxHttpBodyParam setBody(File file, @Nullable MediaType contentType) {\n                    return setBody(new FileRequestBody(file, 0, contentType));\n                }\n                ${isAndroid(\"\"\"\n                public RxHttpBodyParam setBody(Context context, Uri uri) {\n                    return setBody(context, uri, BuildUtil.getMediaTypeByUri(context, uri));\n                }\n                \n                public RxHttpBodyParam setBody(Context context, Uri uri, @Nullable MediaType contentType) {\n                    return setBody(new UriRequestBody(context, uri, 0, contentType));\n                }\n                \"\"\")}\n                //Content-Type: application/json; charset=utf-8\n                public RxHttpBodyParam setBody(Object object) {\n                    param.setBody(object);\n                    return this;\n                }\n                \n                public RxHttpBodyParam setBody(RequestBody requestBody) {\n                    param.setBody(requestBody);\n                    return this;\n                }\n            }\n\n        \"\"\".trimIndent()\n        )\n    }\n\n    private fun generatorRxHttpFormParam(filer: Filer) {\n        generatorClass(filer, \"RxHttpFormParam\", \"\"\"\n            package $rxHttpPackage;\n\n            ${isAndroid(\"import android.content.Context;\")}\n            ${isAndroid(\"import android.net.Uri;\")}\n            ${isAndroid(\"import rxhttp.wrapper.entity.UriRequestBody;\")}\n            \n            import org.jetbrains.annotations.Nullable;\n\n            import java.io.File;\n            import java.util.List;\n            import java.util.Map;\n            import java.util.Map.Entry;\n\n            import okhttp3.Headers;\n            import okhttp3.MediaType;\n            import okhttp3.MultipartBody;\n            import okhttp3.RequestBody;\n            import rxhttp.wrapper.OkHttpCompat;\n            import rxhttp.wrapper.entity.FileRequestBody;\n            import rxhttp.wrapper.entity.UpFile;\n            import rxhttp.wrapper.param.FormParam;\n            import rxhttp.wrapper.utils.BuildUtil;\n            import rxhttp.wrapper.utils.UriUtil;\n\n            /**\n             * Github\n             * https://github.com/liujingxing/rxhttp\n             * https://github.com/liujingxing/rxlife\n             * https://github.com/liujingxing/rxhttp/wiki/FAQ\n             * https://github.com/liujingxing/rxhttp/wiki/更新日志\n             */\n            public class RxHttpFormParam extends RxHttpAbstractBodyParam<FormParam, RxHttpFormParam> {\n                RxHttpFormParam(FormParam param) {\n                    super(param);\n                }\n\n                public RxHttpFormParam add(String key, @Nullable Object value) {\n                    param.add(key, value);\n                    return this;\n                }\n\n                public RxHttpFormParam add(String key, @Nullable Object value, boolean add) {\n                    if (add) param.add(key,value);\n                    return this;\n                }\n                \n                public RxHttpFormParam addAll(Map<String, ?> map) {\n                    param.addAll(map);\n                    return this;\n                }\n\n                public RxHttpFormParam addEncoded(String key, @Nullable Object value) {\n                    param.addEncoded(key, value);\n                    return this;\n                }\n\n                public RxHttpFormParam addAllEncoded(Map<String, ?> map) {\n                    param.addAllEncoded(map);\n                    return this;\n                }\n\n                public RxHttpFormParam removeAllBody() {\n                    param.removeAllBody();\n                    return this;\n                }\n\n                public RxHttpFormParam removeAllBody(String key) {\n                    param.removeAllBody(key);\n                    return this;\n                }\n\n                public RxHttpFormParam set(String key, @Nullable Object value) {\n                    param.set(key, value);\n                    return this;\n                }\n\n                public RxHttpFormParam setEncoded(String key, @Nullable Object value) {\n                    param.setEncoded(key, value);\n                    return this;\n                }\n\n                public RxHttpFormParam addFile(String key, @Nullable File file) {\n                    if (file == null) return this;\n                    return addFile(key, file, file.getName());\n                }\n\n                public RxHttpFormParam addFile(String key, @Nullable String filePath) {\n                    if (filePath == null) return this;\n                    return addFile(key, new File(filePath));\n                }\n\n                public RxHttpFormParam addFile(String key, @Nullable File file, @Nullable String filename) {\n                    if (file == null) return this;\n                    return addFile(new UpFile(key, file, filename));\n                }\n\n                public RxHttpFormParam addFile(UpFile upFile) {\n                    RequestBody requestBody = new FileRequestBody(upFile.getFile(), upFile.getSkipSize(),\n                        BuildUtil.getMediaType(upFile.getFilename()));\n                    return addFormDataPart(upFile.getKey(), upFile.getFilename(), requestBody);\n                }\n\n                public RxHttpFormParam addFiles(List<UpFile> files) {\n                    for (UpFile file : files) {\n                        addFile(file);\n                    }\n                    return this;\n                }\n\n                public <T> RxHttpFormParam addFiles(Map<String, T> fileMap) {\n                    for (Map.Entry<String, T> entry : fileMap.entrySet()) {\n                        addFile(entry.getKey(), entry.getValue());\n                    }\n                    return this;\n                }\n\n                public <T> RxHttpFormParam addFiles(String key, List<T> files) {\n                    for (T file : files) {\n                        addFile(key, file);\n                    }\n                    return this;\n                }\n\n                private void addFile(String key, @Nullable Object file){\n                    if (file instanceof File) {\n                        addFile(key, (File) file);\n                    } else if (file instanceof String) {\n                        addFile(key, file.toString());\n                    } else if (file != null){\n                        throw new IllegalArgumentException(\"Incoming data type exception, it must be String or File\");\n                    }\n                }\n\n                public RxHttpFormParam addPart(byte[] content, @Nullable MediaType contentType) {\n                    return addPart(content, contentType, 0, content.length);\n                }\n\n                public RxHttpFormParam addPart(byte[] content, @Nullable MediaType contentType, int offset,\n                                               int byteCount) {\n                    return addPart(OkHttpCompat.create(contentType, content, offset, byteCount));\n                }\n                ${isAndroid(\"\"\"\n                public RxHttpFormParam addPart(Context context, Uri uri) {\n                    return addPart(context, uri, BuildUtil.getMediaTypeByUri(context, uri));\n                }\n\n                public RxHttpFormParam addPart(Context context, Uri uri, @Nullable MediaType contentType) {\n                    return addPart(new UriRequestBody(context, uri, 0, contentType));\n                }\n\n                public RxHttpFormParam addPart(Context context, String key, Uri uri) {\n                    return addPart(context, key, UriUtil.displayName(uri, context), uri);\n                }\n\n                public RxHttpFormParam addPart(Context context, String key, String fileName, Uri uri) {\n                    return addPart(context, key, fileName, uri, BuildUtil.getMediaTypeByUri(context, uri));\n                }\n\n                public RxHttpFormParam addPart(Context context, String key, Uri uri,\n                                               @Nullable MediaType contentType) {\n                    return addPart(context, key, UriUtil.displayName(uri, context), uri, contentType);\n                }\n\n                public RxHttpFormParam addPart(Context context, String key, String filename, Uri uri,\n                                               @Nullable MediaType contentType) {\n                    return addFormDataPart(key, filename, new UriRequestBody(context, uri, 0, contentType));\n                }\n\n                public RxHttpFormParam addParts(Context context, Map<String, Uri> uriMap) {\n                    for (Entry<String, Uri> entry : uriMap.entrySet()) {\n                        addPart(context, entry.getKey(), entry.getValue());\n                    }\n                    return this;\n                }\n\n                public RxHttpFormParam addParts(Context context, List<Uri> uris) {\n                    for (Uri uri : uris) {\n                        addPart(context, uri);\n                    }\n                    return this;\n                }\n\n                public RxHttpFormParam addParts(Context context, String key, List<Uri> uris) {\n                    for (Uri uri : uris) {\n                        addPart(context, key, uri);\n                    }\n                    return this;\n                }\n\n                public RxHttpFormParam addParts(Context context, List<Uri> uris,\n                                                @Nullable MediaType contentType) {\n                    for (Uri uri : uris) {\n                        addPart(context, uri, contentType);\n                    }\n                    return this;\n                }\n\n                public RxHttpFormParam addParts(Context context, String key, List<Uri> uris,\n                                                @Nullable MediaType contentType) {\n                    for (Uri uri : uris) {\n                        addPart(context, key, uri, contentType);\n                    }\n                    return this;\n                }\n                \"\"\")}\n                public RxHttpFormParam addPart(RequestBody requestBody) {\n                    return addPart(OkHttpCompat.part(requestBody));\n                }\n\n                public RxHttpFormParam addPart(@Nullable Headers headers, RequestBody requestBody) {\n                    return addPart(OkHttpCompat.part(headers, requestBody));\n                }\n\n                public RxHttpFormParam addFormDataPart(String key, @Nullable String fileName, RequestBody requestBody) {\n                    return addPart(OkHttpCompat.part(key, fileName, requestBody));\n                }\n                \n                public RxHttpFormParam addPart(String key, RequestBody requestBody) {\n                    return addFormDataPart(key, null, requestBody);\n                }\n                \n                public RxHttpFormParam addParts(Map<String, RequestBody> partMap) {\n                    for (Entry<String, RequestBody> entry : partMap.entrySet()) {\n                        addPart(entry.getKey(), entry.getValue());\n                    }\n                    return this;\n                }\n                \n                public RxHttpFormParam addPart(MultipartBody.Part part) {\n                    param.addPart(part);\n                    return this;\n                }\n                \n                public RxHttpFormParam addParts(List<MultipartBody.Part> parts) {\n                    for (MultipartBody.Part part: parts) {\n                        addPart(part);\n                    }\n                    return this;\n                }\n\n                //Set content-type to multipart/form-data\n                public RxHttpFormParam setMultiForm() {\n                    return setMultiType(MultipartBody.FORM);\n                }\n\n                //Set content-type to multipart/mixed\n                public RxHttpFormParam setMultiMixed() {\n                    return setMultiType(MultipartBody.MIXED);\n                }\n\n                //Set content-type to multipart/alternative\n                public RxHttpFormParam setMultiAlternative() {\n                    return setMultiType(MultipartBody.ALTERNATIVE);\n                }\n\n                //Set content-type to multipart/digest\n                public RxHttpFormParam setMultiDigest() {\n                    return setMultiType(MultipartBody.DIGEST);\n                }\n\n                //Set content-type to multipart/parallel\n                public RxHttpFormParam setMultiParallel() {\n                    return setMultiType(MultipartBody.PARALLEL);\n                }\n\n                //Set the MIME type\n                public RxHttpFormParam setMultiType(MediaType multiType) {\n                    param.setMultiType(multiType);\n                    return this;\n                }\n            }\n\n        \"\"\".trimIndent())\n    }\n\n    private fun generatorRxHttpJsonParam(filer: Filer) {\n        generatorClass(filer, \"RxHttpJsonParam\", \"\"\"\n            package $rxHttpPackage;\n\n            import com.google.gson.JsonObject;\n            \n            import org.jetbrains.annotations.Nullable;\n\n            import java.util.Map;\n            \n            import rxhttp.wrapper.param.JsonParam;\n            /**\n             * Github\n             * https://github.com/liujingxing/rxhttp\n             * https://github.com/liujingxing/rxlife\n             * https://github.com/liujingxing/rxhttp/wiki/FAQ\n             * https://github.com/liujingxing/rxhttp/wiki/更新日志\n             */\n            public class RxHttpJsonParam extends RxHttpAbstractBodyParam<JsonParam, RxHttpJsonParam> {\n                RxHttpJsonParam(JsonParam param) {\n                    super(param);\n                }\n\n                public RxHttpJsonParam add(String key, @Nullable Object value) {\n                    param.add(key,value);\n                    return this;\n                }\n                \n                public RxHttpJsonParam add(String key, @Nullable Object value, boolean add) {\n                    if (add) param.add(key,value);\n                    return this;\n                }\n                \n                public RxHttpJsonParam addAll(Map<String, ?> map) {\n                    param.addAll(map);\n                    return this;\n                }\n                \n                /**\n                 * 将Json对象里面的key-value逐一取出，添加到另一个Json对象中，\n                 * 输入非Json对象将抛出{@link IllegalStateException}异常\n                 */\n                public RxHttpJsonParam addAll(String jsonObject) {\n                    param.addAll(jsonObject);\n                    return this;\n                }\n\n                /**\n                 * 将Json对象里面的key-value逐一取出，添加到另一个Json对象中\n                 */\n                public RxHttpJsonParam addAll(JsonObject jsonObject) {\n                    param.addAll(jsonObject);\n                    return this;\n                }\n\n                /**\n                 * 添加一个JsonElement对象(Json对象、json数组等)\n                 */\n                public RxHttpJsonParam addJsonElement(String key, String jsonElement) {\n                    param.addJsonElement(key, jsonElement);\n                    return this;\n                }\n            }\n\n        \"\"\".trimIndent())\n    }\n\n    private fun generatorRxHttpJsonArrayParam(filer: Filer) {\n        generatorClass(filer, \"RxHttpJsonArrayParam\", \"\"\"\n            package $rxHttpPackage;\n\n            import com.google.gson.JsonArray;\n            import com.google.gson.JsonObject;\n            \n            import org.jetbrains.annotations.Nullable;\n\n            import java.util.List;\n            import java.util.Map;\n            \n            import rxhttp.wrapper.param.JsonArrayParam;\n\n            /**\n             * Github\n             * https://github.com/liujingxing/rxhttp\n             * https://github.com/liujingxing/rxlife\n             * https://github.com/liujingxing/rxhttp/wiki/FAQ\n             * https://github.com/liujingxing/rxhttp/wiki/更新日志\n             */\n            public class RxHttpJsonArrayParam extends RxHttpAbstractBodyParam<JsonArrayParam, RxHttpJsonArrayParam> {\n                RxHttpJsonArrayParam(JsonArrayParam param) {\n                    super(param);\n                }\n\n                public RxHttpJsonArrayParam add(String key, @Nullable Object value) {\n                    param.add(key,value);\n                    return this;\n                }\n                \n                public RxHttpJsonArrayParam add(String key, @Nullable Object value, boolean add) {\n                    if (add) param.add(key,value);\n                    return this;\n                }\n                \n                public RxHttpJsonArrayParam addAll(Map<String, ?> map) {\n                    param.addAll(map);\n                    return this;\n                }\n\n                public RxHttpJsonArrayParam add(Object object) {\n                    param.add(object);\n                    return this;\n                }\n\n                public RxHttpJsonArrayParam addAll(List<?> list) {\n                    param.addAll(list);\n                    return this;\n                }\n\n                /**\n                 * 添加多个对象，将字符串转JsonElement对象,并根据不同类型,执行不同操作,可输入任意非空字符串\n                 */\n                public RxHttpJsonArrayParam addAll(String jsonElement) {\n                    param.addAll(jsonElement);\n                    return this;\n                }\n\n                public RxHttpJsonArrayParam addAll(JsonArray jsonArray) {\n                    param.addAll(jsonArray);\n                    return this;\n                }\n\n                /**\n                 * 将Json对象里面的key-value逐一取出，添加到Json数组中，成为单独的对象\n                 */\n                public RxHttpJsonArrayParam addAll(JsonObject jsonObject) {\n                    param.addAll(jsonObject);\n                    return this;\n                }\n\n                public RxHttpJsonArrayParam addJsonElement(String jsonElement) {\n                    param.addJsonElement(jsonElement);\n                    return this;\n                }\n\n                /**\n                 * 添加一个JsonElement对象(Json对象、json数组等)\n                 */\n                public RxHttpJsonArrayParam addJsonElement(String key, String jsonElement) {\n                    param.addJsonElement(key, jsonElement);\n                    return this;\n                }\n            }\n\n        \"\"\".trimIndent())\n    }\n\n    private fun generatorClass(filer: Filer, className: String, content: String) {\n        try {\n            val sourceFile = filer.createSourceFile(\"$rxHttpPackage.$className\")\n            BufferedWriter(sourceFile.openWriter()).use {\n                it.write(content)\n            }\n        } catch (ignore: Exception) {\n        }\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/ConverterVisitor.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.rxhttp.compiler.rxhttpClass\nimport com.squareup.javapoet.ClassName\nimport com.squareup.javapoet.MethodSpec\nimport com.squareup.javapoet.TypeVariableName\nimport rxhttp.wrapper.annotation.Converter\nimport java.util.*\nimport javax.annotation.processing.Messager\nimport javax.lang.model.element.Modifier\nimport javax.lang.model.element.TypeElement\nimport javax.lang.model.element.VariableElement\nimport javax.lang.model.util.Types\n\nclass ConverterVisitor(\n    private val types: Types,\n    private val logger: Messager\n) {\n\n    private val elementMap = LinkedHashMap<String, VariableElement>()\n\n    fun add(element: VariableElement) {\n        try {\n            element.checkConverterValidClass(types)\n            val annotation = element.getAnnotation(Converter::class.java)\n            var name = annotation.name\n            if (name.isBlank()) {\n                name = element.simpleName.toString().firstLetterUpperCase()\n            }\n            if (elementMap.containsKey(name)) {\n                val msg =\n                    \"The variable '${element.simpleName}' in the @Converter annotation 'name = $name' is duplicated\"\n                throw NoSuchElementException(msg)\n            }\n            elementMap[name] = element\n        } catch (e: NoSuchElementException) {\n            logger.error(e.message, element)\n        }\n    }\n\n    fun getMethodList(): List<MethodSpec> {\n        val typeVariableR = TypeVariableName.get(\"R\", rxhttpClass)     //泛型R\n        return elementMap.mapNotNull { entry ->\n            val key = entry.key\n            val variableElement = entry.value\n            val className = ClassName.get(variableElement.enclosingElement.asType())\n            MethodSpec.methodBuilder(\"set$key\")\n                .addModifiers(Modifier.PUBLIC)\n                .addStatement(\"return setConverter(\\$T.${variableElement.simpleName})\", className)\n                .returns(typeVariableR)\n                .build()\n        }\n    }\n}\n\n@Throws(NoSuchElementException::class)\nprivate fun VariableElement.checkConverterValidClass(types: Types) {\n    val variableName = simpleName.toString()\n\n    val className = \"rxhttp.wrapper.callback.IConverter\"\n    val typeElement = types.asElement(asType()) as? TypeElement\n    if (!typeElement.instanceOf(className, types)) {\n        throw NoSuchElementException(\"The variable '$variableName' must be IConverter\")\n    }\n\n    var curParent = enclosingElement\n    while (curParent is TypeElement) {\n        if (!curParent.modifiers.contains(Modifier.PUBLIC)) {\n            val msg = \"The class '${curParent.qualifiedName}' must be public\"\n            throw NoSuchElementException(msg)\n        }\n        curParent = curParent.enclosingElement\n    }\n\n    if (!modifiers.contains(Modifier.PUBLIC)) {\n        val msg =\n            \"The variable '$variableName' must be public, please add @JvmField annotation if you use kotlin\"\n        throw NoSuchElementException(msg)\n    }\n    if (!modifiers.contains(Modifier.STATIC)) {\n        throw NoSuchElementException(\"The variable '$variableName' must be static\")\n    }\n}\n\n"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/DefaultDomainVisitor.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.squareup.javapoet.ClassName\nimport com.squareup.javapoet.MethodSpec\nimport javax.annotation.processing.Messager\nimport javax.lang.model.element.Element\nimport javax.lang.model.element.Modifier\nimport javax.lang.model.element.VariableElement\nimport javax.lang.model.util.Types\n\n/**\n * User: ljx\n * Date: 2021/10/17\n * Time: 12:30\n */\nclass DefaultDomainVisitor(\n    private val types: Types,\n    private val logger: Messager\n) {\n\n    private var element: VariableElement? = null\n\n    fun set(elements: Set<Element>) {\n        try {\n            if (elements.size > 1) {\n                val msg = \"@DefaultDomain annotations can only be used once\"\n                throw NoSuchElementException(msg)\n            }\n            (elements.firstOrNull() as? VariableElement)?.apply {\n                checkVariableValidClass(types)\n                element = this\n            }\n        } catch (e: NoSuchElementException) {\n            logger.error(e.message, elements.first())\n        }\n    }\n\n    //对url添加域名方法\n    fun getMethod(): MethodSpec {\n        val methodBuilder = MethodSpec.methodBuilder(\"addDefaultDomainIfAbsent\")\n            .addJavadoc(\"给Param设置默认域名(如果缺席的话)，此方法会在请求发起前，被RxHttp内部调用\\n\")\n            .addModifiers(Modifier.PRIVATE)\n        element?.apply {\n            val className = ClassName.get(enclosingElement.asType())\n            methodBuilder.addCode(\n                $$\"\"\"\n                String originUrl = param.getSimpleUrl();\n                if (originUrl.startsWith(\"http\")) return;\n                setDomainIfAbsent($T.$${simpleName});\n            \"\"\".trimIndent(), className)\n        }\n        return methodBuilder.build()\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/DomainVisitor.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.rxhttp.compiler.rxhttpClass\nimport com.squareup.javapoet.ClassName\nimport com.squareup.javapoet.MethodSpec\nimport com.squareup.javapoet.TypeVariableName\nimport rxhttp.wrapper.annotation.Domain\nimport java.util.*\nimport javax.annotation.processing.Messager\nimport javax.lang.model.element.Modifier\nimport javax.lang.model.element.TypeElement\nimport javax.lang.model.element.VariableElement\nimport javax.lang.model.util.Types\n\nclass DomainVisitor(\n    private val types: Types,\n    private val logger: Messager\n) {\n\n    private val elementMap = LinkedHashMap<String, VariableElement>()\n\n    fun add(element: VariableElement) {\n        try {\n            element.checkVariableValidClass(types)\n            val annotation = element.getAnnotation(Domain::class.java)\n            var name: String = annotation.name\n            if (name.isBlank()) {\n                name = element.simpleName.toString().firstLetterUpperCase()\n            }\n            if (elementMap.containsKey(name)) {\n                val msg =\n                    \"The variable '${element.simpleName}' in the @Domain annotation 'name = $name' is duplicated\"\n                throw NoSuchElementException(msg)\n            }\n            elementMap[name] = element\n        } catch (e: NoSuchElementException) {\n            logger.error(e.message, element)\n        }\n    }\n\n    //对url添加域名方法\n    fun getMethodList(): List<MethodSpec> {\n        val typeVariableR = TypeVariableName.get(\"R\", rxhttpClass)     //泛型R\n        return elementMap.mapNotNull { entry ->\n            val key = entry.key\n            val variableElement = entry.value\n            val className = ClassName.get(variableElement.enclosingElement.asType())\n            MethodSpec.methodBuilder(\"setDomainTo${key}IfAbsent\")\n                .addModifiers(Modifier.PUBLIC)\n                .addCode(\"return setDomainIfAbsent(\\$T.${variableElement.simpleName});\", className)\n                .returns(typeVariableR)\n                .build()\n        }\n    }\n}\n\n@Throws(NoSuchElementException::class)\nfun VariableElement.checkVariableValidClass(types: Types) {\n    val variableName = simpleName.toString()\n\n    val typeElement = types.asElement(asType()) as? TypeElement\n    val className = \"java.lang.String\"\n    if (!typeElement.instanceOf(className, types)) {\n        throw NoSuchElementException(\"The variable '$variableName' must be String\")\n    }\n\n    var curParent = enclosingElement\n    while (curParent is TypeElement) {\n        if (!curParent.modifiers.contains(Modifier.PUBLIC)) {\n            val msg = \"The class '${curParent.qualifiedName}' must be public\"\n            throw NoSuchElementException(msg)\n        }\n        curParent = curParent.enclosingElement\n    }\n\n    if (!modifiers.contains(Modifier.PUBLIC)) {\n        val msg =\n            \"The variable '$variableName' must be public, please add @JvmField annotation if you use kotlin\"\n        throw NoSuchElementException(msg)\n    }\n    if (!modifiers.contains(Modifier.STATIC)) {\n        throw NoSuchElementException(\"The variable '$variableName' must be static\")\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/OkClientVisitor.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.rxhttp.compiler.rxhttpClass\nimport com.squareup.javapoet.ClassName\nimport com.squareup.javapoet.MethodSpec\nimport com.squareup.javapoet.TypeVariableName\nimport rxhttp.wrapper.annotation.OkClient\nimport java.util.*\nimport javax.annotation.processing.Messager\nimport javax.lang.model.element.Modifier\nimport javax.lang.model.element.TypeElement\nimport javax.lang.model.element.VariableElement\nimport javax.lang.model.util.Types\n\nclass OkClientVisitor(\n    private val types: Types,\n    private val logger: Messager\n) {\n\n    private val elementMap = LinkedHashMap<String, VariableElement>()\n\n    fun add(element: VariableElement) {\n        try {\n            element.checkOkClientValidClass(types)\n            val annotation = element.getAnnotation(OkClient::class.java)\n            var name = annotation.name\n            if (name.isBlank()) {\n                name = element.simpleName.toString().firstLetterUpperCase()\n            }\n            if (elementMap.containsKey(name)) {\n                val msg =\n                    \"The variable '${element.simpleName}' in the @OkClient annotation 'name = $name' is duplicated\"\n                throw NoSuchElementException(msg)\n            }\n            elementMap[name] = element\n        } catch (e: NoSuchElementException) {\n            logger.error(e.message, element)\n        }\n    }\n\n    fun getMethodList(): List<MethodSpec> {\n        val typeVariableR = TypeVariableName.get(\"R\", rxhttpClass)   //泛型R\n        return elementMap.mapNotNull { entry ->\n            val key = entry.key\n            val variableElement = entry.value\n            val className = ClassName.get(variableElement.enclosingElement.asType())\n            MethodSpec.methodBuilder(\"set$key\")\n                .addModifiers(Modifier.PUBLIC)\n                .addStatement(\"return setOkClient(\\$T.${variableElement.simpleName})\", className)\n                .returns(typeVariableR)\n                .build()\n        }\n    }\n}\n\n@Throws(NoSuchElementException::class)\nprivate fun VariableElement.checkOkClientValidClass(types: Types) {\n    val variableName = simpleName.toString()\n\n    val className = \"okhttp3.OkHttpClient\"\n    val typeElement = types.asElement(asType()) as? TypeElement\n    if (!typeElement.instanceOf(className, types)) {\n        throw NoSuchElementException(\"The variable '$variableName' must be OkHttpClient\")\n    }\n\n    var curParent = enclosingElement\n    while (curParent is TypeElement) {\n        if (!curParent.modifiers.contains(Modifier.PUBLIC)) {\n            val msg = \"The class '${curParent.qualifiedName}' must be public\"\n            throw NoSuchElementException(msg)\n        }\n        curParent = curParent.enclosingElement\n    }\n\n    if (!modifiers.contains(Modifier.PUBLIC)) {\n        val msg =\n            \"The variable '$variableName' must be public, please add @JvmField annotation if you use kotlin\"\n        throw NoSuchElementException(msg)\n    }\n    if (!modifiers.contains(Modifier.STATIC)) {\n        throw NoSuchElementException(\"The variable '$variableName' must be static\")\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/ParamsVisitor.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.rxhttp.compiler.common.joinToStringIndexed\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpClass\nimport com.squareup.javapoet.ArrayTypeName\nimport com.squareup.javapoet.ClassName\nimport com.squareup.javapoet.JavaFile\nimport com.squareup.javapoet.MethodSpec\nimport com.squareup.javapoet.ParameterSpec\nimport com.squareup.javapoet.ParameterizedTypeName\nimport com.squareup.javapoet.TypeName\nimport com.squareup.javapoet.TypeSpec\nimport com.squareup.javapoet.TypeVariableName\nimport rxhttp.wrapper.annotation.Param\nimport java.io.IOException\nimport java.util.*\nimport javax.annotation.processing.Filer\nimport javax.annotation.processing.Messager\nimport javax.lang.model.element.ElementKind\nimport javax.lang.model.element.ExecutableElement\nimport javax.lang.model.element.Modifier\nimport javax.lang.model.element.TypeElement\nimport javax.lang.model.util.Types\n\nclass ParamsVisitor(private val logger: Messager) {\n\n    private val elementMap = LinkedHashMap<String, TypeElement>()\n\n    fun add(element: TypeElement, types: Types) {\n        try {\n            element.checkParamsValidClass(types)\n            val annotation = element.getAnnotation(Param::class.java)\n            val name = annotation.methodName\n            if (name.isBlank()) {\n                val msg = \"methodName() in @${Param::class.java.simpleName} for class \" +\n                        \"'${element.qualifiedName}' is null or empty! that's not allowed\"\n                throw NoSuchElementException(msg)\n            }\n            elementMap[name] = element\n        } catch (e: NoSuchElementException) {\n            logger.error(e.message, element)\n        }\n    }\n\n    @Throws(IOException::class)\n    fun getMethodList(filer: Filer): List<MethodSpec> {\n        val methodList = ArrayList<MethodSpec>()\n        var method: MethodSpec.Builder\n        elementMap.forEach { (key, typeElement) ->\n            val rxHttpTypeNames = typeElement.typeParameters.map { TypeVariableName.get(it) }\n            val param = ClassName.get(typeElement)\n            val rxHttpName = \"RxHttp${typeElement.simpleName}\"\n            val rxHttpParamName = rxhttpClass.peerClass(rxHttpName)\n            val methodReturnType = if (rxHttpTypeNames.isNotEmpty()) {\n                rxHttpParamName.parameterizedBy(*rxHttpTypeNames.toTypedArray())\n            } else {\n                rxHttpParamName\n            }\n            //遍历public构造方法\n            typeElement.getPublicConstructors().forEach { element ->\n                //构造方法参数\n                val parameterSpecs = element.parameters.mapTo(ArrayList()) { ParameterSpec.get(it) }\n                val prefix = \"return new \\$T(new \\$T(\"\n                val postfix = \"))\"\n                val methodBody = parameterSpecs\n                    .joinToStringIndexed(\", \", prefix, postfix) { index, it ->\n                        if (index == 0 && it.type == STRING) {\n                            \"format(${it.name}, formatArgs)\"\n                        } else it.name\n                    }\n                val firstParamIsStringType = parameterSpecs.firstOrNull()?.type == STRING\n                if (firstParamIsStringType) {\n                    val arrayAny = ArrayTypeName.of(TypeName.OBJECT)\n                    parameterSpecs.add(ParameterSpec.builder(arrayAny, \"formatArgs\").build())\n                }\n                MethodSpec.methodBuilder(key)\n                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)\n                    .addTypeVariables(rxHttpTypeNames)\n                    .addParameters(parameterSpecs)\n                    .varargs(firstParamIsStringType)\n                    .addStatement(methodBody, rxHttpParamName, param)\n                    .returns(methodReturnType)\n                    .build()\n                    .apply { methodList.add(this) }\n            }\n            val superclass = typeElement.superclass\n            var prefix = \"((${typeElement.simpleName})param).\"\n            val rxHttpParam = when (superclass.toString()) {\n                \"rxhttp.wrapper.param.BodyParam\" -> rxhttpClass.peerClass(\"RxHttpBodyParam\")\n                \"rxhttp.wrapper.param.FormParam\" -> rxhttpClass.peerClass(\"RxHttpFormParam\")\n                \"rxhttp.wrapper.param.JsonParam\" -> rxhttpClass.peerClass(\"RxHttpJsonParam\")\n                \"rxhttp.wrapper.param.JsonArrayParam\" -> rxhttpClass.peerClass(\"RxHttpJsonArrayParam\")\n                \"rxhttp.wrapper.param.NoBodyParam\" -> rxhttpClass.peerClass(\"RxHttpNoBodyParam\")\n                else -> {\n                    val typeName = TypeName.get(superclass)\n                    if ((typeName as? ParameterizedTypeName)?.rawType?.toString() == \"rxhttp.wrapper.param.AbstractBodyParam\") {\n                        prefix = \"param.\"\n                        rxhttpClass.peerClass(\"RxHttpAbstractBodyParam\")\n                            .parameterizedBy(param, rxHttpParamName)\n                    } else {\n                        prefix = \"param.\"\n                        rxhttpClass.parameterizedBy(param, rxHttpParamName)\n                    }\n                }\n            }\n            val rxHttpPostCustomMethod = ArrayList<MethodSpec>()\n            MethodSpec.constructorBuilder()\n                .addParameter(param, \"param\")\n                .addStatement(\"super(param)\")\n                .build()\n                .apply { rxHttpPostCustomMethod.add(this) }\n            for (enclosedElement in typeElement.enclosedElements) {\n                if (enclosedElement !is ExecutableElement\n                    || enclosedElement.getKind() != ElementKind.METHOD //过滤非方法，\n                    || !enclosedElement.getModifiers().contains(Modifier.PUBLIC) //过滤非public修饰符\n                    || enclosedElement.getAnnotation(Override::class.java) != null //过滤重写的方法\n                ) continue\n                val returnType = TypeName.get(enclosedElement.returnType).let {\n                    if (it == param) rxHttpParamName else it\n                }\n\n                //方法参数\n                val parameterSpecs = enclosedElement.parameters.map { variableElement ->\n                    ParameterSpec.get(variableElement)\n                }\n                //方法参数名字\n                val paramNames = parameterSpecs.toParamNames()\n                //方法体\n                val methodBody = \"${enclosedElement.getSimpleName()}($paramNames)\"\n                //方法声明的泛型\n                val typeVariableNames = enclosedElement.typeParameters.map {\n                    TypeVariableName.get(it)\n                }\n                //方法要抛出的异常\n                val throwTypeNames = enclosedElement.thrownTypes.map { TypeName.get(it) }\n                method = MethodSpec.methodBuilder(enclosedElement.getSimpleName().toString())\n                    .addModifiers(enclosedElement.getModifiers())\n                    .addTypeVariables(typeVariableNames)\n                    .addExceptions(throwTypeNames)\n                    .addParameters(parameterSpecs)\n                    .varargs(enclosedElement.isVarArgs)\n                when {\n                    returnType === rxHttpParamName -> {\n                        method.addStatement(prefix + methodBody, param)\n                            .addStatement(\"return this\")\n                    }\n                    returnType == TypeName.VOID -> {\n                        method.addStatement(prefix + methodBody)\n                    }\n                    else -> {\n                        method.addStatement(\"return $prefix$methodBody\", param)\n                    }\n                }\n                method.returns(returnType)\n                rxHttpPostCustomMethod.add(method.build())\n            }\n            val rxHttpPostEncryptFormParamSpec = TypeSpec.classBuilder(rxHttpName)\n                .addJavadoc(\n                    \"\"\"\n                    Github\n                    https://github.com/liujingxing/rxhttp\n                    https://github.com/liujingxing/rxlife\n                \"\"\".trimIndent()\n                )\n                .addModifiers(Modifier.PUBLIC)\n                .addTypeVariables(rxHttpTypeNames)\n                .superclass(rxHttpParam)\n                .addMethods(rxHttpPostCustomMethod)\n                .build()\n            JavaFile.builder(rxHttpPackage, rxHttpPostEncryptFormParamSpec)\n                .skipJavaLangImports(true)\n                .build().writeTo(filer)\n        }\n        return methodList\n    }\n}\n\n@Throws(NoSuchElementException::class)\nprivate fun TypeElement.checkParamsValidClass(types: Types) {\n    val paramSimpleName = Param::class.java.simpleName\n    val elementQualifiedName = qualifiedName.toString()\n    if (!modifiers.contains(Modifier.PUBLIC)) {\n        throw NoSuchElementException(\"The class '$elementQualifiedName' must be public\")\n    }\n    if (modifiers.contains(Modifier.ABSTRACT)) {\n        val msg =\n            \"The class '$elementQualifiedName' is abstract. You can't annotate abstract classes with @$paramSimpleName\"\n        throw NoSuchElementException(msg)\n    }\n    val className = \"rxhttp.wrapper.param.Param\"\n    if (!instanceOf(className, types)) {\n        val msg =\n            \"The class '$elementQualifiedName' annotated with @$paramSimpleName must inherit from $className\"\n        throw NoSuchElementException(msg)\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/ParserVisitor.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.rxhttp.compiler.J_ARRAY_TYPE\nimport com.rxhttp.compiler.J_TYPE\nimport com.rxhttp.compiler.common.joinToStringIndexed\nimport com.rxhttp.compiler.isDependenceRxJava\nimport com.rxhttp.compiler.rxhttpClass\nimport com.squareup.javapoet.AnnotationSpec\nimport com.squareup.javapoet.ArrayTypeName\nimport com.squareup.javapoet.ClassName\nimport com.squareup.javapoet.CodeBlock\nimport com.squareup.javapoet.MethodSpec\nimport com.squareup.javapoet.ParameterSpec\nimport com.squareup.javapoet.ParameterizedTypeName\nimport com.squareup.javapoet.TypeName\nimport com.squareup.javapoet.TypeVariableName\nimport rxhttp.wrapper.annotation.Parser\nimport java.util.*\nimport javax.annotation.processing.Filer\nimport javax.annotation.processing.Messager\nimport javax.lang.model.element.Modifier\nimport javax.lang.model.element.TypeElement\nimport javax.lang.model.type.MirroredTypesException\nimport javax.lang.model.util.Types\n\nclass ParserVisitor(private val logger: Messager) {\n\n    private val elementMap = LinkedHashMap<String, TypeElement>()\n    private val typeMap = LinkedHashMap<String, List<ClassName>>()\n\n    fun add(element: TypeElement, types: Types) {\n        try {\n            element.checkParserValidClass(types)\n            val annotation = element.getAnnotation(Parser::class.java)\n            val name: String = annotation.name\n            if (name.isBlank()) {\n                val msg = \"methodName() in @${Parser::class.java.simpleName} for class \" +\n                        \"${element.qualifiedName} is null or empty! that's not allowed\"\n                throw NoSuchElementException(msg)\n            }\n            try {\n                annotation.wrappers\n            } catch (e: MirroredTypesException) {\n                val typeMirrors = e.typeMirrors\n                typeMap[name] = typeMirrors.map { ClassName.bestGuess(it.toString()) }\n            }\n            elementMap[name] = element\n        } catch (e: NoSuchElementException) {\n            logger.error(e.message, element)\n        }\n    }\n\n    fun getMethodList(filer: Filer): List<MethodSpec> {\n        val methodList = ArrayList<MethodSpec>()\n        val rxHttpExtensions = RxHttpExtensions()\n        //遍历自定义解析器\n        elementMap.forEach { (parserAlias, typeElement) ->\n            //生成kotlin编写的toObservableXxx/toAwaitXxx/toFlowXxx方法\n            rxHttpExtensions.generateRxHttpExtendFun(typeElement, parserAlias)\n            //生成Java环境下toObservableXxx方法\n            methodList.addAll(typeElement.getToObservableXxxFun(parserAlias, typeMap))\n        }\n        rxHttpExtensions.generateClassFile(filer)\n        return methodList\n    }\n}\n\n//生成Java语言编写的toObservableXxx方法\nprivate fun TypeElement.getToObservableXxxFun(\n    parserAlias: String,\n    typeMap: LinkedHashMap<String, List<ClassName>>\n): List<MethodSpec> {\n    val methodList = ArrayList<MethodSpec>()\n    //onParser方法返回类型\n    val onParserFunReturnType = findOnParserFunReturnType() ?: return emptyList()\n    val typeVariableNames = typeParameters.map { TypeVariableName.get(it) }\n    val typeCount = typeVariableNames.size  //泛型数量\n    val customParser = ClassName.get(this)\n\n    //遍历构造方法\n    for (constructor in getConstructors()) {\n        if (!constructor.isValid(typeCount)) continue\n\n        //原始参数\n        val originParameterSpecs = constructor.parameters.map { ParameterSpec.get(it) }\n        //将原始参数里的第一个Type数组或Type类型可变参数转换为n个Type类型参数，n为泛型数量\n        val typeParameterSpecs = originParameterSpecs.flapTypeParameterSpecs(typeVariableNames)\n        //将Type类型参数转换为Class<T>类型参数，有泛型时才转\n        val classParameterSpecs = typeParameterSpecs.typeToClassParameterSpecs(typeVariableNames)\n\n        //方法名\n        val methodName = \"toObservable$parserAlias\"\n        //返回类型(Observable<T>类型)\n        val toObservableXxxFunReturnType = rxhttpClass.peerClass(\"ObservableCall\")\n            .parameterizedBy(onParserFunReturnType)\n\n        val types = getTypeVariableString(typeVariableNames) // <T>, <K, V> 等\n        //方法体\n        val toObservableXxxFunBody =\n            if (typeCount == 1 && onParserFunReturnType is TypeVariableName) {\n                val paramNames = typeParameterSpecs.toParamNames()\n                CodeBlock.of(\"return toObservable(wrap${customParser.simpleName()}($paramNames))\")\n            } else {\n                val paramNames = typeParameterSpecs.toParamNames(originParameterSpecs, typeCount)\n                CodeBlock.of(\"return toObservable(new \\$T$types($paramNames))\", customParser)\n            }\n\n        val varargs = constructor.isVarArgs && typeParameterSpecs.last().type is ArrayTypeName\n\n        if (isDependenceRxJava()) {\n            MethodSpec.methodBuilder(methodName)\n                .addModifiers(Modifier.PUBLIC)\n                .addTypeVariables(typeVariableNames)\n                .addParameters(typeParameterSpecs)\n                .varargs(varargs)\n                .addStatement(toObservableXxxFunBody)\n                .returns(toObservableXxxFunReturnType)\n                .build()\n                .apply { methodList.add(this) }\n        }\n\n        if (typeCount == 1 && onParserFunReturnType is TypeVariableName) {\n            val t = TypeVariableName.get(\"T\")\n            val typeUtil = ClassName.get(\"rxhttp.wrapper.utils\", \"TypeUtil\")\n            val okResponseParser = ClassName.get(\"rxhttp.wrapper.parse\", \"OkResponseParser\")\n            val parserClass = okResponseParser.peerClass(\"Parser\").parameterizedBy(t)\n\n            val suppressAnnotation = AnnotationSpec.builder(SuppressWarnings::class.java)\n                .addMember(\"value\", \"\\$S\", \"unchecked\")\n                .build()\n\n            val firstParamName = typeParameterSpecs.first().name\n            val paramNames = typeParameterSpecs.toParamNames(originParameterSpecs, typeCount)\n                .replace(firstParamName, \"actualType\")\n\n            MethodSpec.methodBuilder(\"wrap${customParser.simpleName()}\")\n                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)\n                .addAnnotation(suppressAnnotation)\n                .addTypeVariable(t)\n                .addParameters(typeParameterSpecs)\n                .varargs(varargs)\n                .returns(parserClass)\n                .addCode(\n                    $$\"\"\"\n                    Type actualType = $T.getActualType($$firstParamName);\n                    if (actualType == null) actualType = $$firstParamName;\n                    $T parser = new $T($$paramNames);\n                    return actualType == $$firstParamName ? parser : new $T(parser);\n                \"\"\".trimIndent(), typeUtil, customParser, customParser, okResponseParser\n                ).build().apply { methodList.add(this) }\n        }\n\n        if (typeCount > 0 && isDependenceRxJava()) {\n            val paramNames = classParameterSpecs.toParamNames(typeCount)\n\n            //生成Class类型参数的toObservableXxx方法\n            val methodSpec = MethodSpec.methodBuilder(methodName)\n                .addModifiers(Modifier.PUBLIC)\n                .addTypeVariables(typeVariableNames)\n                .addParameters(classParameterSpecs)\n                .varargs(varargs)\n                .addStatement(\"return $methodName($paramNames)\")  //方法里面的表达式\n                .returns(toObservableXxxFunReturnType)\n                .build()\n                .apply { methodList.add(this) }\n\n            /**\n             * 生成Parser注解里wrappers字段对应的toObservableXxx方法，如满足以下3个条件\n             * 1、泛型数量为1\n             * 2、泛型没有边界(kapt会自动过滤Object类型, 即使手动声明了)\n             * 3、解析器onParse方法返回泛型\n             */\n            if (typeCount == 1 && typeVariableNames.first().bounds.isEmpty() && onParserFunReturnType is TypeVariableName) {\n                val toObservableXxxFunList = methodSpec\n                    .getToObservableXxxWrapFun(parserAlias, onParserFunReturnType, typeMap)\n                methodList.addAll(toObservableXxxFunList)\n            }\n        }\n    }\n    return methodList\n}\n\nprivate fun List<ParameterSpec>.flapTypeParameterSpecs(\n    typeVariableNames: List<TypeVariableName>\n): List<ParameterSpec> {\n    val parameterSpecs = mutableListOf<ParameterSpec>()\n    forEachIndexed { index, parameterSpec ->\n        val firstParamType = parameterSpec.type\n        //对于kapt，数组类型或可变参数类型，得到的皆为数组类型，所以这里无需跟ksp一样判断可变参数类型\n        if (index == 0 && firstParamType == J_ARRAY_TYPE && typeVariableNames.isNotEmpty()) {\n            typeVariableNames.mapTo(parameterSpecs) {\n                val variableName = \"${it.name.lowercase(Locale.getDefault())}Type\"\n                ParameterSpec.builder(J_TYPE, variableName).build()\n            }\n        } else {\n            parameterSpecs.add(parameterSpec)\n        }\n    }\n    return parameterSpecs\n}\n\nprivate fun List<ParameterSpec>.typeToClassParameterSpecs(\n    typeVariableNames: List<TypeVariableName>\n): List<ParameterSpec> {\n    val typeCount = typeVariableNames.size\n    val className = ClassName.get(Class::class.java)\n    return mapIndexed { index, parameterSpec ->\n        if (index < typeCount) {\n            val classType = className.parameterizedBy(typeVariableNames[index])\n            ParameterSpec.builder(classType, parameterSpec.name).build()\n        } else parameterSpec\n    }\n}\n\nprivate fun List<ParameterSpec>.toParamNames(\n    originParamsSpecs: List<ParameterSpec>,\n    typeCount: Int\n): String {\n    val isArrayType = typeCount > 0 && originParamsSpecs.first().type == J_ARRAY_TYPE\n    val paramNames = StringBuilder()\n    if (isArrayType) {\n        paramNames.append(\"new Type[]{\")\n    }\n    forEachIndexed { index, parameterSpec ->\n        if (index > 0) paramNames.append(\", \")\n        paramNames.append(parameterSpec.name)\n        if (isArrayType && index == typeCount - 1) {\n            paramNames.append(\"}\")\n        }\n    }\n    return paramNames.toString()\n}\n\nprivate fun List<ParameterSpec>.toParamNames(typeCount: Int): String {\n    val paramNames = StringBuilder()\n    forEachIndexed { index, parameterSpec ->\n        if (index > 0) paramNames.append(\", \")\n        if (index < typeCount && parameterSpec.type.isClassType()) {\n            paramNames.append(\"(Type) \")\n        }\n        paramNames.append(parameterSpec.name)\n    }\n    return paramNames.toString()\n}\n\n/**\n * 生成Parser注解里wrappers字段指定类对应的toObservableXxx方法\n * @param parserAlias 解析器别名\n * @param onParserFunReturnType 解析器里onParser方法的返回类型\n * @param typeMap Parser注解里wrappers字段集合\n */\nprivate fun MethodSpec.getToObservableXxxWrapFun(\n    parserAlias: String,\n    onParserFunReturnType: TypeName,\n    typeMap: LinkedHashMap<String, List<ClassName>>,\n): List<MethodSpec> {\n    val methodSpec = this //解析器对应的toObservableXxx方法，没有经过wrappers字段包裹前的\n    val methodList = mutableListOf<MethodSpec>()\n    val parameterSpecs = methodSpec.parameters\n    val typeVariableNames = methodSpec.typeVariables\n    val typeCount = typeVariableNames.size\n\n    val parameterizedType = ClassName.get(\"rxhttp.wrapper.entity\", \"ParameterizedTypeImpl\")\n\n    val wrapperListClass = arrayListOf<ClassName>()\n    typeMap[parserAlias]?.apply { wrapperListClass.addAll(this) }\n    val listClassName = ClassName.get(\"java.util\", \"List\")\n    if (listClassName !in wrapperListClass) {\n        wrapperListClass.add(0, listClassName)\n    }\n    wrapperListClass.forEach { wrapperClass ->\n\n        //1、toObservableXxx方法返回值\n        val onParserFunReturnWrapperType =\n            if (onParserFunReturnType is ParameterizedTypeName) {\n                //返回类型有n个泛型，需要对每个泛型再次包装\n                val typeNames = onParserFunReturnType.typeArguments.map { typeArg ->\n                    wrapperClass.parameterizedBy(typeArg)\n                }\n                onParserFunReturnType.rawType.parameterizedBy(*typeNames.toTypedArray())\n            } else {\n                wrapperClass.parameterizedBy(onParserFunReturnType)\n            }\n        val toObservableXxxFunReturnType = rxhttpClass.peerClass(\"ObservableCall\")\n            .parameterizedBy(onParserFunReturnWrapperType)\n\n        //2、toObservableXxx方法名\n        val name = wrapperClass.toString()\n        val simpleName = name.substring(name.lastIndexOf(\".\") + 1)\n        val methodName = \"toObservable$parserAlias${simpleName}\"\n\n        //3、toObservableXxx方法体\n        val methodBody = CodeBlock.builder()\n        val paramNames = parameterSpecs.joinToStringIndexed(\", \") { index, it ->\n            if (index < typeCount) {\n                //Class类型参数，需要进行再次包装，最后再取参数名\n                val variableName = \"${it.name}$simpleName\"\n                //格式：Type tTypeList = ParameterizedTypeImpl.get(List.class, tType);\n                val expression = \"\\$T $variableName = \\$T.get($simpleName.class, ${it.name})\"\n                methodBody.addStatement(expression, J_TYPE, parameterizedType)\n                variableName\n            } else it.name\n        }\n        methodBody.addStatement(\"return ${methodSpec.name}($paramNames)\")\n        //4、生成toObservableXxx方法\n        MethodSpec.methodBuilder(methodName)\n            .addModifiers(Modifier.PUBLIC)\n            .addTypeVariables(typeVariableNames)\n            .addParameters(parameterSpecs)\n            .varargs(methodSpec.varargs)\n            .addCode(methodBody.build())  //方法里面的表达式\n            .returns(toObservableXxxFunReturnType)\n            .build()\n            .apply { methodList.add(this) }\n    }\n    return methodList\n}\nprivate fun TypeName.isClassType() = toString().startsWith(\"java.lang.Class\")\n\n//获取泛型字符串 比如:<T> 、<K,V>等等\nprivate fun getTypeVariableString(typeVariableNames: List<TypeVariableName>): String {\n    return if (typeVariableNames.isNotEmpty()) \"<>\" else \"\"\n}\n\n@Throws(NoSuchElementException::class)\nprivate fun TypeElement.checkParserValidClass(types: Types) {\n    val elementQualifiedName = qualifiedName.toString()\n    if (!modifiers.contains(Modifier.PUBLIC)) {\n        throw NoSuchElementException(\"The class '$elementQualifiedName' must be public\")\n    }\n    if (modifiers.contains(Modifier.ABSTRACT)) {\n        val msg =\n            \"The class '$elementQualifiedName' is abstract. You can't annotate abstract classes with @${Parser::class.java.simpleName}\"\n        throw NoSuchElementException(msg)\n    }\n\n    val className = \"rxhttp.wrapper.parse.Parser\"\n    if (!instanceOf(className, types)) {\n        val msg =\n            \"The class '$elementQualifiedName' annotated with @${Parser::class.java.simpleName} must inherit from $className\"\n        throw NoSuchElementException(msg)\n    }\n\n    val typeParameterList = typeParameters\n    val typeCount = typeParameterList.size\n    if (typeCount > 0) {\n        //查找带 java.lang.reflect.Type 参数的构造方法\n        val isValid = getConstructors().any { it.isValid(typeCount) }\n        if (!isValid) {\n            val method = StringBuffer(\"public ${simpleName}(\")\n            for (i in typeParameterList.indices) {\n                method.append(\"java.lang.reflect.Type\")\n                method.append(if (i == typeParameterList.lastIndex) \")\" else \",\")\n            }\n            val msg =\n                \"This class '$elementQualifiedName' must declare '$method' constructor method\"\n            throw NoSuchElementException(msg)\n        }\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/RxHttpExtensions.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.rxhttp.compiler.common.flapTypeParameterSpecTypes\nimport com.rxhttp.compiler.common.getRxHttpExtensionFileSpec\nimport com.rxhttp.compiler.common.getTypeOfString\nimport com.rxhttp.compiler.common.getTypeVariableString\nimport com.rxhttp.compiler.common.isArrayType\nimport com.rxhttp.compiler.common.toParamNames\nimport com.rxhttp.compiler.isDependenceRxJava\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpKClass\nimport com.squareup.kotlinpoet.BOOLEAN\nimport com.squareup.kotlinpoet.BOOLEAN_ARRAY\nimport com.squareup.kotlinpoet.BYTE\nimport com.squareup.kotlinpoet.BYTE_ARRAY\nimport com.squareup.kotlinpoet.CHAR\nimport com.squareup.kotlinpoet.CHAR_ARRAY\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.DOUBLE\nimport com.squareup.kotlinpoet.DOUBLE_ARRAY\nimport com.squareup.kotlinpoet.FLOAT\nimport com.squareup.kotlinpoet.FLOAT_ARRAY\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.INT\nimport com.squareup.kotlinpoet.INT_ARRAY\nimport com.squareup.kotlinpoet.KModifier\nimport com.squareup.kotlinpoet.LONG\nimport com.squareup.kotlinpoet.LONG_ARRAY\nimport com.squareup.kotlinpoet.MemberName\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.ParameterizedTypeName\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.SHORT\nimport com.squareup.kotlinpoet.SHORT_ARRAY\nimport com.squareup.kotlinpoet.TypeName\nimport com.squareup.kotlinpoet.TypeVariableName\nimport com.squareup.kotlinpoet.U_BYTE_ARRAY\nimport com.squareup.kotlinpoet.U_INT_ARRAY\nimport com.squareup.kotlinpoet.U_LONG_ARRAY\nimport com.squareup.kotlinpoet.U_SHORT_ARRAY\nimport com.squareup.kotlinpoet.javapoet.JClassName\nimport com.squareup.kotlinpoet.javapoet.JTypeName\nimport com.squareup.kotlinpoet.javapoet.JTypeVariableName\nimport com.squareup.kotlinpoet.javapoet.KotlinPoetJavaPoetPreview\nimport com.squareup.kotlinpoet.javapoet.toKClassName\nimport com.squareup.kotlinpoet.javapoet.toKTypeName\nimport com.squareup.kotlinpoet.javapoet.toKTypeVariableName\nimport org.jetbrains.annotations.Nullable\nimport javax.annotation.processing.Filer\nimport javax.lang.model.element.TypeElement\nimport javax.lang.model.type.TypeKind\n\n/**\n * User: ljx\n * Date: 2020/3/9\n * Time: 17:04\n */\nclass RxHttpExtensions {\n\n    private val baseRxHttpName = rxhttpKClass.peerClass(\"BaseRxHttp\")\n    private val callFactoryName = ClassName(\"rxhttp.wrapper\", \"CallFactory\")\n    private val awaitName = ClassName(\"rxhttp.wrapper.coroutines\", \"CallAwait\")\n    private val observableCall = rxhttpKClass.peerClass(\"ObservableCall\")\n\n    private val toFlowXxxFunList = ArrayList<FunSpec>()\n    private val toAwaitXxxFunList = ArrayList<FunSpec>()\n    private val toObservableXxxFunList = ArrayList<FunSpec>()\n\n    //根据@Parser注解，生成toObservableXxx()、toAwaitXxx()、toFlowXxx()系列方法\n    @OptIn(KotlinPoetJavaPoetPreview::class)\n    fun generateRxHttpExtendFun(typeElement: TypeElement, key: String) {\n        //遍历获取泛型类型\n        val typeVariableNames = typeElement.typeParameters.map {\n            JTypeVariableName.get(it).toKTypeVariableName().copy(reified = true)\n        }\n        val onParserFunReturnType = typeElement.findOnParserFunReturnType()?.toKTypeName() ?: return\n        val typeCount = typeVariableNames.size  //泛型数量\n        val customParser = JClassName.get(typeElement).toKClassName()\n        //遍历构造方法\n        for (constructor in typeElement.getConstructors()) {\n            if (!constructor.isValid(typeCount)) continue\n            val parameters = constructor.parameters\n            val varArgsFun = constructor.isVarArgs  //该构造方法是否携带可变参数，即是否为可变参数方法\n            val originParameterSpecs = parameters.mapIndexed { index, variableElement ->\n                val variableType = variableElement.asType()\n                val variableName = variableElement.simpleName.toString()\n                val annotation = variableElement.getAnnotation(Nullable::class.java)\n                var typeName = JTypeName.get(variableType).toKTypeName()\n                val isVarArg = varArgsFun\n                        && index == parameters.lastIndex\n                        && variableType.kind == TypeKind.ARRAY\n                if (isVarArg) {  //最后一个参数是可变参数\n                    typeName = when (typeName) {\n                        BOOLEAN_ARRAY -> BOOLEAN\n                        BYTE_ARRAY, U_BYTE_ARRAY -> BYTE\n                        CHAR_ARRAY -> CHAR\n                        SHORT_ARRAY, U_SHORT_ARRAY -> SHORT\n                        INT_ARRAY, U_INT_ARRAY -> INT\n                        LONG_ARRAY, U_LONG_ARRAY -> LONG\n                        FLOAT_ARRAY -> FLOAT\n                        DOUBLE_ARRAY -> DOUBLE\n                        is ParameterizedTypeName -> typeName.typeArguments.first()\n                        else -> typeName\n                    }\n                }\n                if (annotation != null) typeName = typeName.copy(true)\n                ParameterSpec.builder(variableName, typeName).apply {\n                    if (isVarArg) {\n                        addModifiers(KModifier.VARARG)\n                    }\n                }.build()\n            }\n            val typeParameterSpecs = originParameterSpecs.flapTypeParameterSpecTypes(typeVariableNames)\n            //根据构造方法参数，获取toObservableXxx方法需要的参数\n            val parameterList = typeParameterSpecs.subList(typeCount, typeParameterSpecs.size)\n\n            val modifiers = ArrayList<KModifier>()\n            if (typeVariableNames.isNotEmpty()) {\n                modifiers.add(KModifier.INLINE)\n            }\n\n            val types = typeVariableNames.getTypeVariableString() // <T>, <K, V> 等\n            val typeOfs = typeVariableNames.getTypeOfString()  // javaTypeOf<T>()等\n            val paramNames = parameterList.toParamNames()  //构造方法参数名列表\n            val finalParams = listOf(typeOfs, paramNames)\n                .filter { it.isNotEmpty() }\n                .joinToString()\n\n            if (typeVariableNames.isNotEmpty() && isDependenceRxJava()) {  //对声明了泛型的解析器，生成kotlin编写的toObservableXxx方法\n                val toObservableXxxFunName = \"toObservable$key\"\n                val toObservableXxxFunBody = \"return $toObservableXxxFunName$types($finalParams)\"\n                FunSpec.builder(toObservableXxxFunName)\n                    .addModifiers(modifiers)\n                    .receiver(baseRxHttpName)\n                    .addParameters(parameterList)\n                    .addStatement(toObservableXxxFunBody) //方法里面的表达式\n                    .addTypeVariables(typeVariableNames)\n                    .returns(observableCall.parameterizedBy(onParserFunReturnType))\n                    .build()\n                    .apply { toObservableXxxFunList.add(this) }\n            }\n\n            val funBody: String\n            val argType: TypeName =\n                if (typeCount == 1 && onParserFunReturnType is TypeVariableName) {\n                    val baseRxHttp = ClassName(rxHttpPackage, \"BaseRxHttp\")\n                    val wrapFun = \"%T.wrap${customParser.simpleName}\"\n                    funBody = \"return %M($wrapFun$types($finalParams))\"\n                    baseRxHttp\n                } else {\n                    var params = finalParams\n                    if (typeOfs.isNotEmpty() &&\n                        originParameterSpecs.first().isArrayType() &&\n                        onParserFunReturnType !is TypeVariableName\n                    ) {\n                        params = params.replace(typeOfs, \"arrayOf($typeOfs)\")\n                    }\n                    funBody = \"return %M(%T$types($params))\"\n                    customParser\n                }\n            val toAwait = MemberName(\"rxhttp\", \"toAwait\")\n            FunSpec.builder(\"toAwait$key\")\n                .addModifiers(modifiers)\n                .receiver(callFactoryName)\n                .addParameters(parameterList)\n                .addTypeVariables(typeVariableNames)\n                .addCode(funBody, toAwait, argType)\n                .returns(awaitName.parameterizedBy(onParserFunReturnType))\n                .build()\n                .apply { toAwaitXxxFunList.add(this) }\n\n            val callFlow = ClassName(\"rxhttp.wrapper.coroutines\", \"CallFlow\")\n            val toFlow = MemberName(\"rxhttp\", \"toFlow\")\n            FunSpec.builder(\"toFlow$key\")\n                .addModifiers(modifiers)\n                .receiver(callFactoryName)\n                .addParameters(parameterList)\n                .addTypeVariables(typeVariableNames)\n                .addCode(funBody, toFlow, argType)\n                .returns(callFlow.parameterizedBy(onParserFunReturnType))\n                .build()\n                .apply { toFlowXxxFunList.add(this) }\n        }\n    }\n\n    fun generateClassFile(filer: Filer) {\n        val fileSpec =\n            getRxHttpExtensionFileSpec(toObservableXxxFunList, toAwaitXxxFunList, toFlowXxxFunList)\n        fileSpec.writeTo(filer)\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/RxHttpGenerator.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.rxhttp.compiler.RxHttp\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpClass\nimport com.squareup.javapoet.AnnotationSpec\nimport com.squareup.javapoet.ArrayTypeName\nimport com.squareup.javapoet.ClassName\nimport com.squareup.javapoet.CodeBlock\nimport com.squareup.javapoet.FieldSpec\nimport com.squareup.javapoet.JavaFile\nimport com.squareup.javapoet.MethodSpec\nimport com.squareup.javapoet.ParameterSpec\nimport com.squareup.javapoet.TypeName\nimport com.squareup.javapoet.TypeSpec\nimport com.squareup.javapoet.TypeVariableName\nimport com.squareup.javapoet.WildcardTypeName\nimport org.jetbrains.annotations.NotNull\nimport java.io.IOException\nimport javax.annotation.processing.Filer\nimport javax.lang.model.element.Modifier\nimport kotlin.String\n\nclass RxHttpGenerator {\n    var paramsVisitor: ParamsVisitor? = null\n    var domainVisitor: DomainVisitor? = null\n    var converterVisitor: ConverterVisitor? = null\n    var okClientVisitor: OkClientVisitor? = null\n    var defaultDomainVisitor: DefaultDomainVisitor? = null\n\n    //生成RxHttp类\n    @Throws(IOException::class)\n    fun generateCode(filer: Filer) {\n\n        val p = TypeVariableName.get(\"P\")\n        val r = TypeVariableName.get(\"R\")\n        val paramClassName = ClassName.get(\"rxhttp.wrapper.param\", \"Param\")\n        val typeVariableP = TypeVariableName.get(\"P\", paramClassName.parameterizedBy(p))      //泛型P\n        val typeVariableR = TypeVariableName.get(\"R\", rxhttpClass.parameterizedBy(p, r))     //泛型R\n\n        val okHttpClient = ClassName.get(\"okhttp3\", \"OkHttpClient\")\n        val requestName = okHttpClient.peerClass(\"Request\")\n        val headerName = okHttpClient.peerClass(\"Headers\")\n        val headerBuilderName = okHttpClient.peerClass(\"Headers.Builder\")\n        val cacheControlName = okHttpClient.peerClass(\"CacheControl\")\n        val callName = okHttpClient.peerClass(\"Call\")\n\n        val timeUnitName = ClassName.get(\"java.util.concurrent\", \"TimeUnit\")\n\n        val rxHttpPluginsName = ClassName.get(\"rxhttp\", \"RxHttpPlugins\")\n        val converterName = ClassName.get(\"rxhttp.wrapper.callback\", \"IConverter\")\n        val logUtilName = ClassName.get(\"rxhttp.wrapper.utils\", \"LogUtil\")\n        val logInterceptor = ClassName.get(\"rxhttp.wrapper.intercept\", \"LogInterceptor\")\n        val cacheInterceptorName = logInterceptor.peerClass(\"CacheInterceptor\")\n        val rangeInterceptor = logInterceptor.peerClass(\"RangeInterceptor\")\n        val cacheModeName = ClassName.get(\"rxhttp.wrapper.cache\", \"CacheMode\")\n        val cacheStrategyName = cacheModeName.peerClass(\"CacheStrategy\")\n        val downloadOffSizeName = ClassName.get(\"rxhttp.wrapper.entity\", \"DownloadOffSize\")\n        val outputStreamFactory = converterName.peerClass(\"OutputStreamFactory\")\n\n        val t = TypeVariableName.get(\"T\")\n        val wildcard = TypeVariableName.get(\"?\")\n        val className = ClassName.get(Class::class.java)\n        val superT = WildcardTypeName.supertypeOf(t)\n        val classSuperTName = className.parameterizedBy(superT)\n\n        val list = ClassName.get(List::class.java)\n        val map = ClassName.get(Map::class.java)\n        val string = TypeName.get(String::class.java)\n        val listName = list.parameterizedBy(wildcard)\n        val mapName = map.parameterizedBy(string, wildcard)\n        val mapStringName = map.parameterizedBy(string, string)\n\n        val methodList = ArrayList<MethodSpec>() //方法集合\n\n        MethodSpec.constructorBuilder()\n            .addParameter(typeVariableP, \"param\")\n            .addStatement(\"this.param = param\")\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"getParam\")\n            .addModifiers(Modifier.PUBLIC)\n            .addStatement(\"return param\")\n            .returns(typeVariableP)\n            .build()\n            .apply { methodList.add(this) }\n\n\n        MethodSpec.methodBuilder(\"setParam\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(typeVariableP, \"param\")\n            .addStatement(\"this.param = param\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"connectTimeout\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(TypeName.LONG, \"connectTimeout\")\n            .addStatement(\"connectTimeoutMillis = connectTimeout\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"readTimeout\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(TypeName.LONG, \"readTimeout\")\n            .addStatement(\"readTimeoutMillis = readTimeout\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"writeTimeout\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(TypeName.LONG, \"writeTimeout\")\n            .addStatement(\"writeTimeoutMillis = writeTimeout\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        val methodMap = LinkedHashMap<String, String>()\n        methodMap[\"get\"] = \"RxHttpNoBodyParam\"\n        methodMap[\"head\"] = \"RxHttpNoBodyParam\"\n        methodMap[\"postBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"putBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"patchBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"deleteBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"postForm\"] = \"RxHttpFormParam\"\n        methodMap[\"putForm\"] = \"RxHttpFormParam\"\n        methodMap[\"patchForm\"] = \"RxHttpFormParam\"\n        methodMap[\"deleteForm\"] = \"RxHttpFormParam\"\n        methodMap[\"postJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"putJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"patchJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"deleteJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"postJsonArray\"] = \"RxHttpJsonArrayParam\"\n        methodMap[\"putJsonArray\"] = \"RxHttpJsonArrayParam\"\n        methodMap[\"patchJsonArray\"] = \"RxHttpJsonArrayParam\"\n        methodMap[\"deleteJsonArray\"] = \"RxHttpJsonArrayParam\"\n\n        val codeBlock = CodeBlock.builder()\n            .add(\n                \"\"\"\n                    For example:\n                                                             \n                    ```                                                  \n                    RxHttp.get(\"/service/%d/...\", 1)  \n                        .addQuery(\"size\", 20)\n                        ...\n                    ```\n                     url = /service/1/...?size=20\n                \"\"\".trimIndent()\n            )\n            .build()\n\n        methodMap.forEach { (key, value) ->\n            val methodBuilder = MethodSpec.methodBuilder(key)\n            if (key == \"get\") {\n                methodBuilder.addJavadoc(codeBlock)\n            }\n            methodBuilder\n                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)\n                .addParameter(string, \"url\")\n                .addParameter(ArrayTypeName.of(TypeName.OBJECT), \"formatArgs\")\n                .varargs()\n                .addStatement(\n                    \"return new $value(\\$T.${key}(format(url, formatArgs)))\",\n                    paramClassName,\n                )\n                .returns(rxhttpClass.peerClass(value))\n                .build()\n                .apply { methodList.add(this) }\n        }\n\n        paramsVisitor?.apply {\n            methodList.addAll(getMethodList(filer))\n        }\n\n        MethodSpec.methodBuilder(\"setUrl\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"url\")\n            .addStatement(\"param.setUrl(url)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addPath\")\n            .addModifiers(Modifier.PUBLIC)\n            .addJavadoc(\n                \"\"\"\n                For example:\n                                                         \n                ```                                                  \n                RxHttp.get(\"/service/{page}/...\")  \n                    .addPath(\"page\", 1)\n                    ...\n                ```\n                url = /service/1/...\n                \"\"\".trimIndent()\n            )\n            .addParameter(string, \"name\")\n            .addParameter(TypeName.OBJECT, \"value\")\n            .addStatement(\"param.addPath(name, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addEncodedPath\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"name\")\n            .addParameter(TypeName.OBJECT, \"value\")\n            .addStatement(\"param.addEncodedPath(name, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(TypeName.OBJECT, \"value\")\n            .addStatement(\"param.setQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(TypeName.OBJECT, \"value\")\n            .addParameter(TypeName.BOOLEAN, \"add\")\n            .addStatement(\"if (add) param.setQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setEncodedQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(TypeName.OBJECT, \"value\")\n            .addStatement(\"param.setEncodedQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setEncodedQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(TypeName.OBJECT, \"value\")\n            .addParameter(TypeName.BOOLEAN, \"add\")\n            .addStatement(\"if (add) param.setEncodedQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n\n        MethodSpec.methodBuilder(\"removeAllQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addStatement(\"param.removeAllQuery(key)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addStatement(\"param.addQuery(key, null)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addEncodedQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addStatement(\"param.addEncodedQuery(key, null)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(TypeName.OBJECT, \"value\")\n            .addStatement(\"param.addQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(TypeName.OBJECT, \"value\")\n            .addParameter(TypeName.BOOLEAN, \"add\")\n            .addStatement(\"if (add) param.addQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addEncodedQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(TypeName.OBJECT, \"value\")\n            .addStatement(\"param.addEncodedQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addEncodedQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(TypeName.OBJECT, \"value\")\n            .addParameter(TypeName.BOOLEAN, \"add\")\n            .addStatement(\"if (add) param.addEncodedQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addAllQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(listName, \"list\")\n            .addStatement(\"param.addAllQuery(key, list)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addAllEncodedQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(listName, \"list\")\n            .addStatement(\"param.addAllEncodedQuery(key, list)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addAllQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(mapName, \"map\")\n            .addStatement(\"param.addAllQuery(map)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addAllEncodedQuery\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(mapName, \"map\")\n            .addStatement(\"param.addAllEncodedQuery(map)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"line\")\n            .addStatement(\"param.addHeader(line)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"line\")\n            .addParameter(TypeName.BOOLEAN, \"add\")\n            .addStatement(\"if (add) param.addHeader(line)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addNonAsciiHeader\")\n            .addJavadoc(\"Add a header with the specified name and value. Does validation of header names, allowing non-ASCII values.\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(string, \"value\")\n            .addStatement(\"param.addNonAsciiHeader(key,value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setNonAsciiHeader\")\n            .addJavadoc(\"Set a header with the specified name and value. Does validation of header names, allowing non-ASCII values.\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(string, \"value\")\n            .addStatement(\"param.setNonAsciiHeader(key,value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(string, \"value\")\n            .addStatement(\"param.addHeader(key,value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(string, \"value\")\n            .addParameter(TypeName.BOOLEAN, \"add\")\n            .addStatement(\"if (add) param.addHeader(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addAllHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(mapStringName, \"headers\")\n            .addStatement(\"param.addAllHeader(headers)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"addAllHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(headerName, \"headers\")\n            .addStatement(\"param.addAllHeader(headers)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addParameter(string, \"value\")\n            .addStatement(\"param.setHeader(key,value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setAllHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(mapStringName, \"headers\")\n            .addStatement(\"param.setAllHeader(headers)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setRangeHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(TypeName.LONG, \"startIndex\")\n            .addStatement(\"return setRangeHeader(startIndex, -1, false)\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setRangeHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(TypeName.LONG, \"startIndex\")\n            .addParameter(TypeName.LONG, \"endIndex\")\n            .addStatement(\"return setRangeHeader(startIndex, endIndex, false)\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setRangeHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(TypeName.LONG, \"startIndex\")\n            .addParameter(TypeName.BOOLEAN, \"connectLastProgress\")\n            .addStatement(\"return setRangeHeader(startIndex, -1, connectLastProgress)\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setRangeHeader\")\n            .addJavadoc(\n                \"\"\"\n                设置断点下载开始/结束位置\n                @param startIndex 断点下载开始位置\n                @param endIndex 断点下载结束位置，默认为-1，即默认结束位置为文件末尾\n                @param connectLastProgress 是否衔接上次的下载进度，该参数仅在带进度断点下载时生效\n                \"\"\".trimIndent()\n            )\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(TypeName.LONG, \"startIndex\")\n            .addParameter(TypeName.LONG, \"endIndex\")\n            .addParameter(TypeName.BOOLEAN, \"connectLastProgress\")\n            .addCode(\n                $$\"\"\"\n                param.setRangeHeader(startIndex, endIndex);                         \n                if (connectLastProgress && startIndex >= 0)                                            \n                    param.tag(DownloadOffSize.class, new $T(startIndex));\n                return self();                                                    \n                \"\"\".trimIndent(), downloadOffSizeName\n            )\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"removeAllHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addStatement(\"param.removeAllHeader(key)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setHeadersBuilder\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(headerBuilderName, \"builder\")\n            .addStatement(\"param.setHeadersBuilder(builder)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setAssemblyEnabled\")\n            .addJavadoc(\n                \"\"\"\n                设置单个接口是否需要添加公共参数,\n                即是否回调{@link RxHttpPlugins#setOnParamAssembly(rxhttp.wrapper.callback.Consumer)}方法设置的接口, 默认为true\n                \"\"\".trimIndent()\n            )\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(TypeName.BOOLEAN, \"enabled\")\n            .addStatement(\"param.setAssemblyEnabled(enabled)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setDecoderEnabled\")\n            .addJavadoc(\n                \"\"\"\n                设置单个接口是否需要对Http返回的数据进行解码/解密,\n                即是否回调{@link RxHttpPlugins#setResultDecoder(rxhttp.wrapper.callback.Function)}方法设置的接口, 默认为true\n                \"\"\".trimIndent()\n            )\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(TypeName.BOOLEAN, \"enabled\")\n            .addStatement(\n                \"param.addHeader(\\$T.DATA_DECRYPT,String.valueOf(enabled))\",\n                paramClassName\n            )\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"isAssemblyEnabled\")\n            .addModifiers(Modifier.PUBLIC)\n            .addStatement(\"return param.isAssemblyEnabled()\")\n            .returns(TypeName.BOOLEAN)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"getUrl\")\n            .addModifiers(Modifier.PUBLIC)\n            .addStatement(\"addDefaultDomainIfAbsent()\")\n            .addStatement(\"return param.getUrl()\")\n            .returns(string)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"getSimpleUrl\")\n            .addModifiers(Modifier.PUBLIC)\n            .addStatement(\"return param.getSimpleUrl()\")\n            .returns(string)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"getHeader\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"key\")\n            .addStatement(\"return param.getHeader(key)\")\n            .returns(string).build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"getHeaders\")\n            .addModifiers(Modifier.PUBLIC)\n            .addStatement(\"return param.getHeaders()\")\n            .returns(headerName)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"getHeadersBuilder\")\n            .addModifiers(Modifier.PUBLIC)\n            .addStatement(\"return param.getHeadersBuilder()\")\n            .returns(headerBuilderName).build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"tag\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(TypeName.OBJECT, \"tag\")\n            .addStatement(\"param.tag(tag)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        val typeParam = ParameterSpec.builder(classSuperTName, \"type\")\n            .addAnnotation(NotNull::class.java)\n            .build()\n\n        MethodSpec.methodBuilder(\"tag\")\n            .addAnnotation(NotNull::class.java)\n            .addAnnotation(Override::class.java)\n            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n            .addTypeVariable(t)\n            .addParameter(typeParam)\n            .addParameter(t, \"tag\")\n            .addCode(\n                $$\"\"\"\n            param.tag(type, tag);\n            if (type == $T.class) {\n                okClient = okClient.newBuilder()\n                    .addInterceptor(new $T())\n                    .build();\n            }\n            return self();\n            \"\"\".trimIndent(), outputStreamFactory, rangeInterceptor\n            )\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"cacheControl\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(cacheControlName, \"cacheControl\")\n            .addStatement(\"param.cacheControl(cacheControl)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"getCacheStrategy\")\n            .addModifiers(Modifier.PUBLIC)\n            .addStatement(\"return param.getCacheStrategy()\")\n            .returns(cacheStrategyName)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setCacheKey\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"cacheKey\")\n            .addStatement(\"param.setCacheKey(cacheKey)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setCacheValidTime\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(TypeName.LONG, \"cacheValidTime\")\n            .addStatement(\"param.setCacheValidTime(cacheValidTime)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setCacheMode\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(cacheModeName, \"cacheMode\")\n            .addStatement(\"param.setCacheMode(cacheMode)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"newCall\")\n            .addAnnotation(NotNull::class.java)\n            .addAnnotation(Override::class.java)\n            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n            .addCode(\n                \"\"\"\n                Request request = buildRequest();\n                OkHttpClient okClient = getOkHttpClient();\n                return okClient.newCall(request);\n                \"\"\".trimIndent()\n            )\n            .returns(callName)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"buildRequest\")\n            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)\n            .addCode(\n                \"\"\"\n                if (request == null) {\n                    doOnStart();\n                    request = param.buildRequest();\n                }\n                return request;\n                \"\"\".trimIndent()\n            )\n            .returns(requestName)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"getOkHttpClient\")\n            .addModifiers(Modifier.PUBLIC)\n            .addCode(\n                $$\"\"\"\n                if (realOkClient != null) return realOkClient;\n                final OkHttpClient okClient = this.okClient;\n                OkHttpClient.Builder builder = null;\n                \n                if ($T.isDebug()) {\n                    builder = okClient.newBuilder();\n                    builder.addInterceptor(new $T(okClient));\n                }\n                \n                if (connectTimeoutMillis != 0L) {\n                    if (builder == null) builder = okClient.newBuilder();\n                    builder.connectTimeout(connectTimeoutMillis, $T.MILLISECONDS);\n                }\n                \n                if (readTimeoutMillis != 0L) {\n                    if (builder == null) builder = okClient.newBuilder();\n                    builder.readTimeout(readTimeoutMillis, $T.MILLISECONDS);\n                }\n\n                if (writeTimeoutMillis != 0L) {\n                   if (builder == null) builder = okClient.newBuilder();\n                   builder.writeTimeout(writeTimeoutMillis, $T.MILLISECONDS);\n                }\n                \n                if (param.getCacheMode() != CacheMode.ONLY_NETWORK) {                      \n                    if (builder == null) builder = okClient.newBuilder();              \n                    builder.addInterceptor(new $T(getCacheStrategy()));\n                }\n                                                                                        \n                realOkClient = builder != null ? builder.build() : okClient;\n                return realOkClient;\n                \"\"\".trimIndent(),\n                logUtilName,\n                logInterceptor,\n                timeUnitName,\n                timeUnitName,\n                timeUnitName,\n                cacheInterceptorName\n            )\n            .returns(okHttpClient)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"doOnStart\")\n            .addModifiers(Modifier.PRIVATE, Modifier.FINAL)\n            .addJavadoc(\"请求开始前内部调用，用于添加默认域名等操作\\n\")\n            .addStatement(\"setConverterToParam(converter)\")\n            .addStatement(\"addDefaultDomainIfAbsent()\")\n            .build()\n            .apply { methodList.add(this) }\n\n        converterVisitor?.apply {\n            methodList.addAll(getMethodList())\n        }\n\n        MethodSpec.methodBuilder(\"setConverter\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(converterName, \"converter\")\n            .addCode(\n                \"\"\"\n                if (converter == null)\n                    throw new IllegalArgumentException(\"converter can not be null\");\n                this.converter = converter;\n                return self();\n                \"\"\".trimIndent()\n            )\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setConverterToParam\")\n            .addJavadoc(\"给Param设置转换器，此方法会在请求发起前，被RxHttp内部调用\\n\")\n            .addModifiers(Modifier.PRIVATE)\n            .addParameter(converterName, \"converter\")\n            .addStatement(\"param.tag(IConverter.class, converter)\")\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"setOkClient\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(okHttpClient, \"okClient\")\n            .addCode(\n                \"\"\"\n                if (okClient == null) \n                    throw new IllegalArgumentException(\"okClient can not be null\");\n                this.okClient = okClient;\n                return self();\n                \"\"\".trimIndent()\n            )\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        okClientVisitor?.apply {\n            methodList.addAll(getMethodList())\n        }\n        defaultDomainVisitor?.apply {\n            methodList.add(getMethod())\n        }\n        domainVisitor?.apply {\n            methodList.addAll(getMethodList())\n        }\n\n        MethodSpec.methodBuilder(\"setDomainIfAbsent\")\n            .addModifiers(Modifier.PUBLIC)\n            .addParameter(string, \"domain\")\n            .addCode(\n                \"\"\"\n                String newUrl = addDomainIfAbsent(param.getSimpleUrl(), domain);\n                param.setUrl(newUrl);\n                return self();\n                \"\"\".trimIndent()\n            )\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        //对url添加域名方法\n        MethodSpec.methodBuilder(\"addDomainIfAbsent\")\n            .addModifiers(Modifier.PRIVATE, Modifier.STATIC)\n            .addParameter(string, \"url\")\n            .addParameter(string, \"domain\")\n            .addCode(\n                \"\"\"\n                if (url.startsWith(\"http\")) {\n                    return url;\n                } else if (url.startsWith(\"/\")) {\n                    String finalUrl = domain.endsWith(\"/\") ? url.substring(1) : url;\n                    return domain + finalUrl;\n                } else if (domain.endsWith(\"/\")) {\n                    return domain + url;\n                } else {\n                    return domain + \"/\" + url;\n                }\n                \"\"\".trimIndent()\n            )\n            .returns(string)\n            .build()\n            .apply { methodList.add(this) }\n\n        val suppressWarningsAnnotation = AnnotationSpec.builder(SuppressWarnings::class.java)\n            .addMember(\"value\", \"\\$S\", \"unchecked\")\n            .build()\n\n        MethodSpec.methodBuilder(\"self\")\n            .addAnnotation(suppressWarningsAnnotation)\n            .addModifiers(Modifier.PRIVATE)\n            .addStatement(\"return (R) this\")\n            .returns(typeVariableR)\n            .build()\n            .apply { methodList.add(this) }\n\n        MethodSpec.methodBuilder(\"format\")\n            .addJavadoc(\"Returns a formatted string using the specified format string and arguments.\")\n            .addModifiers(Modifier.PRIVATE, Modifier.STATIC)\n            .addParameter(string, \"url\")\n            .addParameter(ArrayTypeName.of(TypeName.OBJECT), \"formatArgs\")\n            .varargs()\n            .addStatement(\"return formatArgs == null || formatArgs.length == 0 ? url : String.format(url, formatArgs)\")\n            .returns(string)\n            .build()\n            .apply { methodList.add(this) }\n\n        val converterSpec = FieldSpec.builder(converterName, \"converter\", Modifier.PROTECTED)\n            .initializer(\"\\$T.getConverter()\", rxHttpPluginsName)\n            .build()\n\n        val okHttpClientSpec = FieldSpec.builder(okHttpClient, \"okClient\", Modifier.PRIVATE)\n            .initializer(\"\\$T.getOkHttpClient()\", rxHttpPluginsName)\n            .build()\n\n        val baseRxHttpName = rxhttpClass.peerClass(\"BaseRxHttp\")\n\n        val rxHttpBuilder = TypeSpec.classBuilder(RxHttp)\n            .addJavadoc(\n                \"\"\"\n                Github\n                https://github.com/liujingxing/rxhttp\n                https://github.com/liujingxing/rxlife\n                https://github.com/liujingxing/rxhttp/wiki/FAQ\n                https://github.com/liujingxing/rxhttp/wiki/更新日志\n            \"\"\".trimIndent()\n            )\n            .addModifiers(Modifier.PUBLIC)\n            .addField(TypeName.LONG, \"connectTimeoutMillis\", Modifier.PRIVATE)\n            .addField(TypeName.LONG, \"readTimeoutMillis\", Modifier.PRIVATE)\n            .addField(TypeName.LONG, \"writeTimeoutMillis\", Modifier.PRIVATE)\n            .addField(okHttpClient, \"realOkClient\", Modifier.PRIVATE)\n            .addField(okHttpClientSpec)\n            .addField(converterSpec)\n            .addField(typeVariableP, \"param\", Modifier.PROTECTED)\n            .addField(requestName, \"request\", Modifier.PUBLIC)\n            .superclass(baseRxHttpName)\n            .addTypeVariable(typeVariableP)\n            .addTypeVariable(typeVariableR)\n            .addMethods(methodList)\n\n        // Write file\n        JavaFile.builder(rxHttpPackage, rxHttpBuilder.build())\n            .skipJavaLangImports(true)\n            .indent(\"    \")\n            .build().writeTo(filer)\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/RxHttpWrapper.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpClass\nimport com.squareup.javapoet.ArrayTypeName\nimport com.squareup.javapoet.CodeBlock\nimport com.squareup.javapoet.JavaFile\nimport com.squareup.javapoet.MethodSpec\nimport com.squareup.javapoet.ParameterSpec\nimport com.squareup.javapoet.TypeName\nimport com.squareup.javapoet.TypeSpec\nimport com.squareup.javapoet.TypeVariableName\nimport rxhttp.wrapper.annotation.Converter\nimport rxhttp.wrapper.annotation.Domain\nimport rxhttp.wrapper.annotation.OkClient\nimport rxhttp.wrapper.annotation.Param\nimport javax.annotation.processing.Filer\nimport javax.annotation.processing.Messager\nimport javax.lang.model.element.ElementKind\nimport javax.lang.model.element.ExecutableElement\nimport javax.lang.model.element.Modifier\nimport javax.lang.model.element.TypeElement\nimport javax.lang.model.element.VariableElement\n\n/**\n * User: ljx\n * Date: 2020/5/30\n * Time: 19:03\n */\nclass RxHttpWrapper(private val logger: Messager) {\n\n    private val classMap = LinkedHashMap<String, Wrapper>()\n\n    private val elementMap = LinkedHashMap<String, TypeElement>()\n\n    fun add(typeElement: TypeElement) {\n        val annotation = typeElement.getAnnotation(Param::class.java)\n        val name: String = annotation.methodName\n        elementMap[name] = typeElement\n    }\n\n    fun addOkClient(variableElement: VariableElement) {\n        val okClient = variableElement.getAnnotation(OkClient::class.java)\n        if (okClient.className.isEmpty()) return\n        var wrapper = classMap[okClient.className]\n        if (wrapper == null) {\n            wrapper = Wrapper()\n            classMap[okClient.className] = wrapper\n        }\n        if (wrapper.okClientName != null) {\n            val msg = \"@OkClient annotation className cannot be the same\"\n            logger.error(msg, variableElement)\n        }\n        var name = okClient.name\n        if (name.isBlank()) {\n            name = variableElement.simpleName.toString().firstLetterUpperCase()\n        }\n        wrapper.okClientName = name\n    }\n\n\n    fun addConverter(variableElement: VariableElement) {\n        val converter = variableElement.getAnnotation(Converter::class.java)\n        if (converter.className.isEmpty()) return\n        var wrapper = classMap[converter.className]\n        if (wrapper == null) {\n            wrapper = Wrapper()\n            classMap[converter.className] = wrapper\n        }\n        if (wrapper.converterName != null) {\n            val msg = \"@Converter annotation className cannot be the same\"\n            logger.error(msg, variableElement)\n        }\n        var name = converter.name\n        if (name.isBlank()) {\n            name = variableElement.simpleName.toString().firstLetterUpperCase()\n        }\n        wrapper.converterName = name\n    }\n\n    fun addDomain(variableElement: VariableElement) {\n        val domain = variableElement.getAnnotation(Domain::class.java)\n        if (domain.className.isEmpty()) return\n        var wrapper = classMap[domain.className]\n        if (wrapper == null) {\n            wrapper = Wrapper()\n            classMap[domain.className] = wrapper\n        }\n        if (wrapper.domainName != null) {\n            val msg = \"@Domain annotation className cannot be the same\"\n            logger.error(msg, variableElement)\n        }\n        var name = domain.name\n        if (name.isBlank()) {\n            name = variableElement.simpleName.toString().firstLetterUpperCase()\n        }\n        wrapper.domainName = name\n    }\n\n    fun generateRxWrapper(filer: Filer) {\n        val requestFunList = generateRequestFunList()\n        val typeVariableR = TypeVariableName.get(\"R\", rxhttpClass)     //泛型R\n        //生成多个RxHttp的包装类\n        classMap.forEach { (className, wrapper) ->\n            val funBody = CodeBlock.builder()\n            wrapper.converterName?.let {\n                funBody.addStatement(\"rxHttp.set$it()\")\n            }\n            wrapper.okClientName?.let {\n                funBody.addStatement(\"rxHttp.set$it()\")\n            }\n            wrapper.domainName?.let {\n                funBody.addStatement(\"rxHttp.setDomainTo${it}IfAbsent()\")\n            }\n            val methodList = ArrayList<MethodSpec>()\n            MethodSpec.methodBuilder(\"wrapper\")\n                .addJavadoc(\"本类所有方法都会调用本方法\")\n                .addTypeVariable(typeVariableR)\n                .addModifiers(Modifier.PRIVATE, Modifier.STATIC)\n                .addParameter(typeVariableR, \"rxHttp\")\n                .addCode(funBody.build())\n                .addStatement(\"return rxHttp\")\n                .returns(typeVariableR)\n                .build()\n                .apply { methodList.add(this) }\n            methodList.addAll(requestFunList)\n\n            val rxHttpBuilder = TypeSpec.classBuilder(\"Rx${className}Http\")\n                .addJavadoc(\n                    \"\"\"\n                    本类由@Converter、@Domain、@OkClient注解中的className字段生成  类命名方式: Rx + {className字段值} + Http\n                    Github\n                    https://github.com/liujingxing/rxhttp\n                    https://github.com/liujingxing/rxlife\n                    https://github.com/liujingxing/rxhttp/wiki/FAQ\n                    https://github.com/liujingxing/rxhttp/wiki/更新日志\n                \"\"\".trimIndent()\n                )\n                .addModifiers(Modifier.PUBLIC)\n                .addMethods(methodList)\n\n            JavaFile.builder(rxHttpPackage, rxHttpBuilder.build())\n                .skipJavaLangImports(true)\n                .build().writeTo(filer)\n        }\n    }\n\n\n    private fun generateRequestFunList(): ArrayList<MethodSpec> {\n        val arrayObject = ArrayTypeName.of(TypeName.OBJECT)\n        val methodList = ArrayList<MethodSpec>() //方法集合\n        val methodMap = LinkedHashMap<String, String>()\n        methodMap[\"get\"] = \"RxHttpNoBodyParam\"\n        methodMap[\"head\"] = \"RxHttpNoBodyParam\"\n        methodMap[\"postBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"putBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"patchBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"deleteBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"postForm\"] = \"RxHttpFormParam\"\n        methodMap[\"putForm\"] = \"RxHttpFormParam\"\n        methodMap[\"patchForm\"] = \"RxHttpFormParam\"\n        methodMap[\"deleteForm\"] = \"RxHttpFormParam\"\n        methodMap[\"postJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"putJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"patchJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"deleteJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"postJsonArray\"] = \"RxHttpJsonArrayParam\"\n        methodMap[\"putJsonArray\"] = \"RxHttpJsonArrayParam\"\n        methodMap[\"patchJsonArray\"] = \"RxHttpJsonArrayParam\"\n        methodMap[\"deleteJsonArray\"] = \"RxHttpJsonArrayParam\"\n        methodMap.forEach { (key, value) ->\n            val returnType = rxhttpClass.peerClass(value)\n            MethodSpec.methodBuilder(key)\n                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)\n                .addParameter(STRING, \"url\")\n                .addParameter(arrayObject, \"formatArgs\")\n                .varargs()\n                .addStatement(\"return wrapper(RxHttp.${key}(url, formatArgs))\")\n                .returns(returnType)\n                .build().apply { methodList.add(this) }\n        }\n\n        elementMap.forEach { (key, typeElement) ->\n            val rxHttpTypeNames = typeElement.typeParameters.map {\n                TypeVariableName.get(it)\n            }\n\n            val rxHttpParamName = rxhttpClass.peerClass(\"RxHttp${typeElement.simpleName}\")\n            val methodReturnType = if (rxHttpTypeNames.isNotEmpty()) {\n                rxHttpParamName.parameterizedBy(*rxHttpTypeNames.toTypedArray())\n            } else {\n                rxHttpParamName\n            }\n\n            //遍历public构造方法\n            getConstructorFun(typeElement).forEach { element ->\n                //构造方法参数\n                val parameterSpecs = element.parameters.mapTo(ArrayList()) { ParameterSpec.get(it) }\n                val firstParamIsStringType = parameterSpecs.firstOrNull()?.type == STRING\n                if (firstParamIsStringType) {\n                    parameterSpecs.add(ParameterSpec.builder(arrayObject, \"formatArgs\").build())\n                }\n                val prefix = \"return wrapper(RxHttp.$key(\"\n                val postfix = \"))\"\n                val methodBody = parameterSpecs.toParamNames(prefix, postfix)\n                MethodSpec.methodBuilder(key)\n                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)\n                    .addTypeVariables(rxHttpTypeNames)\n                    .addParameters(parameterSpecs)\n                    .varargs(firstParamIsStringType)\n                    .addStatement(methodBody)\n                    .returns(methodReturnType)\n                    .build()\n                    .apply { methodList.add(this) }\n            }\n        }\n        return methodList\n    }\n\n    //获取构造方法\n    private fun getConstructorFun(typeElement: TypeElement): MutableList<ExecutableElement> {\n        val funList = java.util.ArrayList<ExecutableElement>()\n        typeElement.enclosedElements.forEach {\n            if (it is ExecutableElement\n                && it.kind == ElementKind.CONSTRUCTOR\n                && it.getModifiers().contains(Modifier.PUBLIC)\n            ) {\n                funList.add(it)\n            }\n        }\n        return funList\n    }\n\n    class Wrapper {\n        var domainName: String? = null\n        var converterName: String? = null\n        var okClientName: String? = null\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/Utils.kt",
    "content": "package com.rxhttp.compiler.kapt\n\nimport com.rxhttp.compiler.J_ARRAY_TYPE\nimport com.rxhttp.compiler.J_TYPE\nimport com.squareup.javapoet.ClassName\nimport com.squareup.javapoet.ParameterSpec\nimport com.squareup.javapoet.ParameterizedTypeName\nimport com.squareup.javapoet.TypeName\nimport javax.annotation.processing.Messager\nimport javax.lang.model.element.Element\nimport javax.lang.model.element.ElementKind\nimport javax.lang.model.element.ExecutableElement\nimport javax.lang.model.element.Modifier\nimport javax.lang.model.element.TypeElement\nimport javax.lang.model.type.TypeKind\nimport javax.lang.model.util.Types\nimport javax.tools.Diagnostic.Kind\n\nval STRING: TypeName = ClassName.get(String::class.java)\nfun List<ParameterSpec>.toParamNames(\n    prefix: CharSequence = \"\",\n    postfix: CharSequence = \"\"\n): String = joinToString(\", \", prefix, postfix) { it.name }\n\n\n//判断解析器构造方法是否有效\nfun ExecutableElement.isValid(typeCount: Int): Boolean {\n    //1、非public方法，无效\n    if (!modifiers.contains(Modifier.PUBLIC)) return false\n    val parameters = parameters\n    if (parameters.isEmpty()) {\n        //2、构造方法没有参数，且泛型数量等于0，有效，反之无效\n        return typeCount == 0\n    }\n    val firstParameter = parameters.first()\n    val firstParameterType = TypeName.get(firstParameter.asType())\n    if (firstParameterType == J_ARRAY_TYPE) {\n        //3、第一个参数为Type类型数组 或 Type类型可变参数, 有效\n        return true\n    }\n    //4、构造方法参数数量小于泛型数量，无效\n    if (parameters.size < typeCount) return false\n    //5、构造方法前n个参数，皆为Type类型，有效  n为泛型数量\n    return parameters.take(typeCount).all { J_TYPE == TypeName.get(it.asType()) }\n}\n\n//获取onParser方法返回类型\nfun TypeElement.findOnParserFunReturnType(): TypeName? {\n    val function = enclosedElements.find {\n        it is ExecutableElement   //是方法\n                && it.getModifiers().contains(Modifier.PUBLIC)  //public修饰\n                && !it.getModifiers().contains(Modifier.STATIC) //非静态\n                && it.simpleName.toString() == \"onParse\"  //onParse方法\n                && it.parameters.size == 1  //只有一个参数\n                && TypeName.get(it.parameters[0].asType())\n            .toString() == \"okhttp3.Response\"  //参数是okhttp3.Response类型\n    } ?: return null\n    return TypeName.get((function as ExecutableElement).returnType)\n}\n\n//获取public构造方法\nfun TypeElement.getPublicConstructors() =\n    getVisibleConstructorFun().filter {\n        it.modifiers.contains(Modifier.PUBLIC)\n    }\n\n//获取 public、protected 构造方法\nfun TypeElement.getVisibleConstructorFun(): List<ExecutableElement> {\n    val funList = ArrayList<ExecutableElement>()\n    enclosedElements.forEach {\n        if (it is ExecutableElement &&\n            it.kind == ElementKind.CONSTRUCTOR &&\n            (Modifier.PUBLIC in it.getModifiers() || Modifier.PROTECTED in it.getModifiers())\n        ) {\n            funList.add(it)\n        }\n    }\n    return funList\n}\n\nfun TypeElement.getConstructors(): List<ExecutableElement> {\n    return enclosedElements.mapNotNull {\n        if (it is ExecutableElement &&\n            it.kind == ElementKind.CONSTRUCTOR &&\n            (Modifier.PUBLIC in it.getModifiers() || Modifier.PROTECTED in it.getModifiers())\n        ) {\n            it\n        } else null\n    }\n}\n\ninternal fun TypeElement?.instanceOf(className: String, types: Types): Boolean {\n    if (this == null) return false\n    if (className == qualifiedName.toString()) return true\n    superTypes().forEach {\n        val typeElement = types.asElement(it) as? TypeElement\n        if (typeElement.instanceOf(className, types)) return true\n    }\n    return false\n}\n\ninternal fun TypeElement.superTypes() =\n    interfaces.toMutableList().apply {\n        if (superclass.kind != TypeKind.NONE)\n            add(0, superclass)\n    }\n\ninternal fun Messager.error(msg: CharSequence?, e: Element) {\n    printMessage(Kind.ERROR, msg ?: \"\", e)\n}\n\ninternal fun String.firstLetterUpperCase(): String {\n    val charArray = toCharArray()\n    val firstChar = charArray.firstOrNull() ?: return this\n    if (firstChar.code in 97..122) {\n        charArray[0] = firstChar.minus(32)\n    }\n    return String(charArray)\n}\n\nfun ClassName.parameterizedBy(vararg typeArguments: TypeName): ParameterizedTypeName =\n    ParameterizedTypeName.get(this, *typeArguments)"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/maven/KaptRxJava2Processor.kt",
    "content": "package com.rxhttp.compiler.kapt.maven\n\nimport com.rxhttp.compiler.KaptProcessor\n\n/**\n * User: ljx\n * Date: 2020/8/8\n * Time: 10:30\n */\nclass AnnotationRxJava2Processor : KaptProcessor() {\n\n    override fun getRxJavaVersion(map: Map<String, String>) = \"rxjava2\"\n\n    override fun isAndroidPlatform() = false\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/kapt/maven/KaptRxJava3Processor.kt",
    "content": "package com.rxhttp.compiler.kapt.maven\n\nimport com.rxhttp.compiler.KaptProcessor\n\n/**\n * User: ljx\n * Date: 2020/8/8\n * Time: 10:30\n */\nclass AnnotationRxJava3Processor : KaptProcessor() {\n\n    override fun getRxJavaVersion(map: Map<String, String>) = \"3.1.1\"\n\n    override fun isAndroidPlatform() = false\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/BaseRxHttpGenerator.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.rxhttp.compiler.getClassPath\nimport com.rxhttp.compiler.isDependenceRxJava\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpKClass\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.CodeBlock\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.KModifier\nimport com.squareup.kotlinpoet.LIST\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.STRING\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.TypeVariableName\nimport com.squareup.kotlinpoet.asClassName\nimport com.squareup.kotlinpoet.jvm.jvmOverloads\nimport com.squareup.kotlinpoet.jvm.throws\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport java.io.IOException\n\nclass BaseRxHttpGenerator(\n    private val logger: KSPLogger,\n    private val isAndroidPlatform: Boolean,\n    private val defaultKsFile: KSFile?,\n    private val parserVisitor: ParserVisitor,\n) {\n    //生成BaseRxHttp类\n    @KspExperimental\n    @Throws(IOException::class)\n    fun generateCode(codeGenerator: CodeGenerator) {\n        val t = TypeVariableName(\"T\")\n        val classTName = Class::class.asClassName().parameterizedBy(\"T\")\n        val listTName = LIST.parameterizedBy(\"T\")\n\n        val parser = ClassName(\"rxhttp.wrapper.parse\", \"Parser\")\n        val parserTName = parser.parameterizedBy(\"T\")\n        val smartParser = parser.peerClass(\"SmartParser\")\n        val streamParser = parser.peerClass(\"StreamParser\")\n        val rxJavaPlugins = ClassName.bestGuess(getClassPath(\"RxJavaPlugins\"))\n        val logUtil = ClassName(\"rxhttp.wrapper.utils\", \"LogUtil\")\n        val responseName = ClassName(\"okhttp3\", \"Response\")\n        val type = ClassName(\"java.lang.reflect\", \"Type\")\n        val outputStreamFactory = ClassName(\"rxhttp.wrapper.callback\", \"OutputStreamFactory\")\n        val fileOutputStreamFactory = outputStreamFactory.peerClass(\"FileOutputStreamFactory\")\n        val uriOutputStreamFactory = outputStreamFactory.peerClass(\"UriOutputStreamFactory\")\n        val observableCall = rxhttpKClass.peerClass(\"ObservableCall\")\n        val observableCallT = observableCall.parameterizedBy(\"T\")\n\n        val methodList = ArrayList<FunSpec>() //方法集合\n\n        if (isDependenceRxJava()) {\n            FunSpec.builder(\"toObservable\")\n                .addTypeVariable(t)\n                .addParameter(\"parser\", parserTName)\n                .addStatement(\"return ObservableCall(this, parser)\")\n                .returns(observableCallT)\n                .build()\n                .let { methodList.add(it) }\n\n            FunSpec.builder(\"toObservable\")\n                .addTypeVariable(t)\n                .addParameter(\"type\", type)\n                .addStatement(\"return toObservable(%T.wrap<T>(type))\", smartParser)\n                .returns(observableCallT)\n                .build()\n                .let { methodList.add(it) }\n\n            FunSpec.builder(\"toObservable\")\n                .addTypeVariable(t)\n                .addParameter(\"clazz\", classTName)\n                .addStatement(\"return toObservable(clazz as Type)\")\n                .returns(observableCallT)\n                .build()\n                .let { methodList.add(it) }\n\n            FunSpec.builder(\"toObservableString\")\n                .addStatement(\"return toObservable(String::class.java)\")\n                .returns(observableCall.parameterizedBy(\"String\"))\n                .build()\n                .let { methodList.add(it) }\n\n            FunSpec.builder(\"toObservableList\")\n                .addTypeVariable(t)\n                .addParameter(\"clazz\", classTName)\n                .addCode(\"\"\"\n                    val typeList = List::class.parameterizedBy(clazz)\n                    return toObservable(typeList)\n                \"\"\".trimIndent())\n                .returns(observableCall.parameterizedBy(\"List<T>\"))\n                .build()\n                .let { methodList.add(it) }\n\n            val appendParam = ParameterSpec.builder(\"append\", Boolean::class)\n                .defaultValue(\"false\")\n                .build()\n\n            FunSpec.builder(\"toDownloadObservable\")\n                .jvmOverloads()\n                .addParameter(\"destPath\", String::class)\n                .addParameter(appendParam)\n                .addStatement(\n                    \"return toDownloadObservable(%T(destPath), append)\",\n                    fileOutputStreamFactory\n                )\n                .returns(observableCall.parameterizedBy(\"String\"))\n                .build()\n                .let { methodList.add(it) }\n\n\n            if (isAndroidPlatform) {\n                val context = ClassName(\"android.content\", \"Context\")\n                val uri = ClassName(\"android.net\", \"Uri\")\n                FunSpec.builder(\"toDownloadObservable\")\n                    .jvmOverloads()\n                    .addParameter(\"context\", context)\n                    .addParameter(\"uri\", uri)\n                    .addParameter(appendParam)\n                    .addStatement(\n                        \"return toDownloadObservable(%T(context, uri), append)\",\n                        uriOutputStreamFactory\n                    )\n                    .returns(observableCall.parameterizedBy(\"Uri\"))\n                    .build()\n                    .let { methodList.add(it) }\n            }\n\n            FunSpec.builder(\"toDownloadObservable\")\n                .jvmOverloads()\n                .addTypeVariable(t)\n                .addParameter(\"osFactory\", outputStreamFactory.parameterizedBy(\"T\"))\n                .addParameter(appendParam)\n                .addCode(\n                    \"\"\"\n                if (append) {\n                    tag(OutputStreamFactory::class.java, osFactory)\n                }\n                return toObservable(%T(osFactory))\n            \"\"\".trimIndent(), streamParser\n                )\n                .returns(observableCall.parameterizedBy(\"T\"))\n                .build()\n                .let { methodList.add(it) }\n        }\n\n        val companionFunList = mutableListOf<FunSpec>()\n        methodList.addAll(parserVisitor.getFunList(codeGenerator, companionFunList, defaultKsFile))\n\n        FunSpec.builder(\"execute\")\n            .throws(IOException::class)\n            .addStatement(\"return newCall().execute()\")\n            .returns(responseName)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"execute\")\n            .addTypeVariable(t)\n            .throws(IOException::class)\n            .addParameter(\"parser\", parserTName)\n            .addStatement(\"return parser.onParse(execute())\")\n            .returns(t)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"executeClass\")\n            .addTypeVariable(t)\n            .throws(IOException::class)\n            .addParameter(\"type\", type)\n            .addStatement(\"return execute(%T.wrap(type))\", smartParser)\n            .returns(t)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"executeClass\")\n            .addTypeVariable(t)\n            .throws(IOException::class)\n            .addParameter(\"clazz\", classTName)\n            .addStatement(\"return executeClass(clazz as Type)\")\n            .returns(t)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"executeString\")\n            .throws(IOException::class)\n            .addStatement(\"return executeClass(String::class.java)\")\n            .returns(STRING)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"executeList\")\n            .addTypeVariable(t)\n            .throws(IOException::class)\n            .addParameter(\"clazz\", classTName)\n            .addCode(\"\"\"\n                val typeList = List::class.parameterizedBy(clazz)\n                return executeClass(typeList)\n            \"\"\".trimIndent())\n            .returns(listTName)\n            .build()\n            .let { methodList.add(it) }\n\n        val fileName = \"BaseRxHttp\"\n        val typeSpecBuilder = TypeSpec.classBuilder(fileName)\n\n        if (isDependenceRxJava() || companionFunList.isNotEmpty()) {\n            val companionBuilder = TypeSpec.companionObjectBuilder()\n            if (isDependenceRxJava()) {\n                val codeBlock = CodeBlock.of(\n                    \"\"\"\n                val errorHandler = %T.getErrorHandler()\n                if (errorHandler == null) {\n                    /*                                                                     \n                     RxJava的一个重要的设计理念是：不吃掉任何一个异常, 即抛出的异常无人处理，便会导致程序崩溃                      \n                     这就会导致一个问题，当RxJava“downStream”取消订阅后，“upStream”仍有可能抛出异常，                \n                     这时由于已经取消订阅，“downStream”无法处理异常，此时的异常无人处理，便会导致程序崩溃                       \n                    */\n                    RxJavaPlugins.setErrorHandler { %T.logRxJavaError(it) }\n                }\n                \n            \"\"\".trimIndent(), rxJavaPlugins, logUtil\n                )\n                companionBuilder.addInitializerBlock(codeBlock)\n            }\n            if (companionFunList.isNotEmpty()) {\n                companionBuilder.addFunctions(companionFunList)\n            }\n            typeSpecBuilder.addType(companionBuilder.build())\n        }\n\n        typeSpecBuilder\n            .addModifiers(KModifier.ABSTRACT)\n            .addSuperinterface(ClassName(\"rxhttp.wrapper\", \"ITag\"))\n            .addSuperinterface(ClassName(\"rxhttp.wrapper\", \"CallFactory\"))\n            .addFunctions(methodList)\n            .addKdoc(\n                \"\"\"\n                User: ljx\n                Date: 2020/4/11\n                Time: 18:15\n            \"\"\".trimIndent()\n            )\n\n        val kSFiles = mutableListOf<KSFile>()\n        kSFiles.addAll(parserVisitor.originatingKSFiles)\n        if (kSFiles.isEmpty()) {\n            defaultKsFile?.let { kSFiles.add(it) }\n        }\n\n        FileSpec.builder(rxHttpPackage, fileName)\n            .addImport(\"rxhttp.wrapper.utils\", \"parameterizedBy\")\n            .addType(typeSpecBuilder.build())\n            .indent(\"    \")\n            .build()\n            .writeTo(codeGenerator, true, kSFiles)\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/ClassHelper.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.Dependencies\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.rxhttp.compiler.common.getObservableClass\nimport com.rxhttp.compiler.isDependenceRxJava\nimport com.rxhttp.compiler.rxHttpPackage\n\n\n/**\n * User: ljx\n * Date: 2020/3/31\n * Time: 23:36\n */\nclass ClassHelper {\n\n    fun generatorStaticClass(codeGenerator: CodeGenerator) {\n        if (isDependenceRxJava()) {\n            getObservableClass().forEach { (t, u) ->\n                generatorClass(codeGenerator, t, u)\n            }\n        }\n    }\n\n    private fun generatorClass(codeGenerator: CodeGenerator, className: String, content: String) {\n        codeGenerator.createNewFile(\n            Dependencies(false, *emptyList<KSFile>().toTypedArray()),\n            rxHttpPackage,\n            className,\n            \"java\"\n        ).use { \n            it.write(content.toByteArray())\n        }\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/ConverterVisitor.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAnnotationsByType\nimport com.google.devtools.ksp.isPublic\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.symbol.ClassKind\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.google.devtools.ksp.symbol.KSPropertyDeclaration\nimport com.google.devtools.ksp.symbol.KSVisitorVoid\nimport com.google.devtools.ksp.symbol.Modifier\nimport com.rxhttp.compiler.rxhttpKClass\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.TypeVariableName\nimport rxhttp.wrapper.annotation.Converter\nimport java.util.*\n\nclass ConverterVisitor(\n    private val resolver: Resolver,\n    private val logger: KSPLogger\n) : KSVisitorVoid() {\n\n    private val elementMap = LinkedHashMap<String, KSPropertyDeclaration>()\n\n    val originatingKSFiles: List<KSFile>\n        get() = elementMap.values.map { it.containingFile!! }\n\n    @KspExperimental\n    override fun visitPropertyDeclaration(property: KSPropertyDeclaration, data: Unit) {\n        try {\n            property.checkConverterProperty(resolver)\n            val annotation = property.getAnnotationsByType(Converter::class).firstOrNull()\n            var name = annotation?.name\n            if (name.isNullOrBlank()) {\n                name = property.simpleName.asString().firstLetterUpperCase()\n            }\n            if (elementMap.containsKey(name)) {\n                val msg =\n                    \"The variable '${property.simpleName.asString()}' in the @Converter annotation 'name = $name' is duplicated\"\n                throw NoSuchElementException(msg)\n            }\n            elementMap[name] = property\n        } catch (e: NoSuchElementException) {\n            logger.error(e, property)\n        }\n    }\n\n    fun getFunList(): List<FunSpec> {\n        val typeVariableR = TypeVariableName(\"R\", rxhttpKClass.parameterizedBy(\"P\", \"R\")) //泛型R\n        return elementMap.mapNotNull { entry ->\n            val key = entry.key\n            val ksProperty = entry.value\n            val memberName = ksProperty.toMemberName()\n            FunSpec.builder(\"set$key\")\n                .addCode(\"return setConverter(%M)\", memberName)\n                .returns(typeVariableR)\n                .build()\n        }\n    }\n}\n\n@Throws(NoSuchElementException::class)\nprivate fun KSPropertyDeclaration.checkConverterProperty(resolver: Resolver) {\n    val variableName = simpleName.asString()\n\n    val className = \"rxhttp.wrapper.callback.IConverter\"\n    if (!type.resolve().instanceOf(className, resolver)) {\n        throw NoSuchElementException(\"The variable '$variableName' must be IConverter\")\n    }\n\n    var curParent = parent\n    while (curParent is KSClassDeclaration) {\n        if (!curParent.isPublic()) {\n            val msg = \"The class '${curParent.qualifiedName?.asString()}' must be public\"\n            throw NoSuchElementException(msg)\n        }\n        curParent = curParent.parent\n    }\n\n    if (!isPublic()) {\n        throw NoSuchElementException(\"The variable '$variableName' must be public\")\n    }\n\n    if (isJava() && Modifier.JAVA_STATIC !in modifiers) {\n        throw NoSuchElementException(\"The variable '$variableName' must be static\")\n    }\n\n    if (isKotlin()) {\n        val parent = parent\n        //在kt文件里，说明是顶级变量，属于合法，直接返回\n        if (parent is KSFile) return\n        //在伴生对象里面，是合法的，直接返回\n        if ((parent as? KSClassDeclaration)?.isCompanionObject == true) return\n\n        if ((parent as? KSClassDeclaration)?.classKind != ClassKind.OBJECT) {\n            //必需要声明在object对象里\n            throw NoSuchElementException(\"The variable '$variableName' must be declared in the object\")\n        }\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/DefaultDomainVisitor.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.google.devtools.ksp.symbol.KSPropertyDeclaration\nimport com.google.devtools.ksp.symbol.KSVisitorVoid\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.KModifier\n\n/**\n * User: ljx\n * Date: 2021/10/17\n * Time: 12:30\n */\nclass DefaultDomainVisitor(\n    private val resolver: Resolver,\n    private val logger: KSPLogger\n) : KSVisitorVoid() {\n\n    private var property: KSPropertyDeclaration? = null\n\n    val originatingKSFile: KSFile?\n        get() = property?.containingFile\n\n    override fun visitPropertyDeclaration(property: KSPropertyDeclaration, data: Unit) {\n        try {\n            if (this.property != null) {\n                val msg = \"@DefaultDomain annotations can only be used once\"\n                throw NoSuchElementException(msg)\n            }\n            property.checkDomainProperty(resolver)\n            this.property = property\n        } catch (e: NoSuchElementException) {\n            logger.error(e, property)\n        }\n    }\n\n    //对url添加域名方法\n    fun getFun(): FunSpec {\n        val methodBuilder = FunSpec.builder(\"addDefaultDomainIfAbsent\")\n            .addKdoc(\"给Param设置默认域名(如果缺席的话)，此方法会在请求发起前，被RxHttp内部调用\\n\")\n            .addModifiers(KModifier.PRIVATE)\n        property?.let { ksProperty ->\n            val memberName = ksProperty.toMemberName()\n            methodBuilder.addCode(\"\"\"\n                val originUrl = param.simpleUrl\n                if (originUrl.startsWith(\"http\")) return\n                setDomainIfAbsent(%M)\n            \"\"\".trimIndent(), memberName)\n        }\n        return methodBuilder.build()\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/DomainVisitor.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAnnotationsByType\nimport com.google.devtools.ksp.isPublic\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.symbol.ClassKind\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.google.devtools.ksp.symbol.KSPropertyDeclaration\nimport com.google.devtools.ksp.symbol.KSVisitorVoid\nimport com.google.devtools.ksp.symbol.Modifier\nimport com.rxhttp.compiler.rxhttpKClass\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.TypeVariableName\nimport rxhttp.wrapper.annotation.Domain\nimport java.util.*\n\n/**\n * User: ljx\n * Date: 2021/10/16\n * Time: 20:17\n */\nclass DomainVisitor(\n    private val resolver: Resolver,\n    private val logger: KSPLogger\n) : KSVisitorVoid() {\n\n    private val elementMap = LinkedHashMap<String, KSPropertyDeclaration>()\n\n    val originatingKSFiles: List<KSFile>\n        get() = elementMap.values.map { it.containingFile!! }\n\n    @KspExperimental\n    override fun visitPropertyDeclaration(property: KSPropertyDeclaration, data: Unit) {\n        try {\n            property.checkDomainProperty(resolver)\n            val annotation = property.getAnnotationsByType(Domain::class).firstOrNull()\n            var name = annotation?.name\n            if (name.isNullOrBlank()) {\n                name = property.simpleName.asString().firstLetterUpperCase()\n            }\n            if (elementMap.containsKey(name)) {\n                val msg =\n                    \"The variable '${property.simpleName.asString()}' in the @Domain annotation 'name = $name' is duplicated\"\n                throw NoSuchElementException(msg)\n            }\n            elementMap[name] = property\n        } catch (e: NoSuchElementException) {\n            logger.error(e, property)\n        }\n    }\n\n\n    //对url添加域名方法\n    fun getFunList(): List<FunSpec> {\n        val typeVariableR = TypeVariableName(\"R\", rxhttpKClass.parameterizedBy(\"P\", \"R\")) //泛型R\n        return elementMap.mapNotNull { entry ->\n            val key = entry.key\n            val ksProperty = entry.value\n            val memberName = ksProperty.toMemberName()\n            FunSpec.builder(\"setDomainTo${key}IfAbsent\")\n                .addCode(\"return setDomainIfAbsent(%M)\", memberName)\n                .returns(typeVariableR)\n                .build()\n        }\n    }\n}\n\n@Throws(NoSuchElementException::class)\nfun KSPropertyDeclaration.checkDomainProperty(resolver: Resolver) {\n    val variableName = simpleName.asString()\n\n    val className = \"kotlin.String\"\n    if (!type.resolve().instanceOf(className, resolver)) {\n        throw NoSuchElementException(\"The variable '$variableName' must be String\")\n    }\n\n    var curParent = parent\n    while (curParent is KSClassDeclaration) {\n        if (!curParent.isPublic()) {\n            val msg = \"The class '${curParent.qualifiedName?.asString()}' must be public\"\n            throw NoSuchElementException(msg)\n        }\n        curParent = curParent.parent\n    }\n\n    if (!isPublic()) {\n        throw NoSuchElementException(\"The variable '$variableName' must be public\")\n    }\n\n    if (isJava() && Modifier.JAVA_STATIC !in modifiers) {\n        throw NoSuchElementException(\"The variable '$variableName' must be static\")\n    }\n\n    if (isKotlin()) {\n        val parent = parent\n        //在kt文件里，说明是顶级变量，属于合法，直接返回\n        if (parent is KSFile) return\n        //在伴生对象里面，是合法的，直接返回\n        if ((parent as? KSClassDeclaration)?.isCompanionObject == true) return\n\n        if ((parent as? KSClassDeclaration)?.classKind != ClassKind.OBJECT) {\n            //必需要声明在object对象里\n            throw NoSuchElementException(\"The variable '$variableName' must be declared in the object\")\n        }\n    }\n}\n"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/KClassHelper.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.Dependencies\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.rxhttp.compiler.rxHttpPackage\n\n\n/**\n * User: ljx\n * Date: 2020/3/31\n * Time: 23:36\n */\nclass KClassHelper(\n    private val isAndroidPlatform: Boolean\n) {\n\n    private fun isAndroid(s: String) = if (isAndroidPlatform) s else \"\"\n\n    fun generatorStaticClass(codeGenerator: CodeGenerator) {\n        generatorRxHttpAbstractBodyParam(codeGenerator)\n        generatorRxHttpBodyParam(codeGenerator)\n        generatorRxHttpFormParam(codeGenerator)\n        generatorRxHttpNoBodyParam(codeGenerator)\n        generatorRxHttpJsonParam(codeGenerator)\n        generatorRxHttpJsonArrayParam(codeGenerator)\n    }\n\n    private fun generatorRxHttpAbstractBodyParam(codeGenerator: CodeGenerator) {\n        generatorClass(\n            codeGenerator, \"RxHttpAbstractBodyParam\", \"\"\"\n                package $rxHttpPackage\n                \n                import rxhttp.wrapper.BodyParamFactory\n                import rxhttp.wrapper.param.AbstractBodyParam\n\n                /**\n                 * Github\n                 * https://github.com/liujingxing/rxhttp\n                 * https://github.com/liujingxing/rxlife\n                 * https://github.com/liujingxing/rxhttp/wiki/FAQ\n                 * https://github.com/liujingxing/rxhttp/wiki/更新日志\n                 */\n                open class RxHttpAbstractBodyParam<P : AbstractBodyParam<P>, R : RxHttpAbstractBodyParam<P, R>> \n                protected constructor(\n                    param: P\n                ) : RxHttp<P, R>(param), BodyParamFactory {\n\n                }\n            \"\"\".trimIndent()\n        )\n    }\n\n    private fun generatorRxHttpNoBodyParam(codeGenerator: CodeGenerator) {\n        generatorClass(\n            codeGenerator, \"RxHttpNoBodyParam\", \"\"\"\n            package $rxHttpPackage\n\n            import rxhttp.wrapper.param.NoBodyParam\n\n            /**\n             * Github\n             * https://github.com/liujingxing/rxhttp\n             * https://github.com/liujingxing/rxlife\n             * https://github.com/liujingxing/rxhttp/wiki/FAQ\n             * https://github.com/liujingxing/rxhttp/wiki/更新日志\n             */\n            open class RxHttpNoBodyParam(param: NoBodyParam) : RxHttp<NoBodyParam, RxHttpNoBodyParam>(param) {\n            \n                @JvmOverloads\n                fun add(key: String, value: Any?, add: Boolean = true) = apply {\n                    if (add) addQuery(key, value)\n                }\n            \n                fun addAll(map: Map<String, *>) = addAllQuery(map)\n            \n                fun addEncoded(key: String, value: Any?) = addEncodedQuery(key, value)\n            \n                fun addAllEncoded(map: Map<String, *>) = addAllEncodedQuery(map)\n            }\n\n        \"\"\".trimIndent()\n        )\n    }\n\n\n    private fun generatorRxHttpBodyParam(codeGenerator: CodeGenerator) {\n        generatorClass(\n            codeGenerator, \"RxHttpBodyParam\", \"\"\"\n            package $rxHttpPackage\n            ${isAndroid(\"\"\"\n            import android.content.Context\n            import android.net.Uri\n            import rxhttp.wrapper.entity.UriRequestBody\n            \"\"\")}\n            import okhttp3.MediaType\n            import okhttp3.RequestBody\n            import okio.ByteString\n            import rxhttp.wrapper.param.BodyParam\n            import rxhttp.wrapper.OkHttpCompat\n            import rxhttp.wrapper.entity.FileRequestBody\n            import rxhttp.wrapper.utils.BuildUtil\n            import java.io.File\n\n            /**\n             * Github\n             * https://github.com/liujingxing/rxhttp\n             * https://github.com/liujingxing/rxlife\n             */\n            open class RxHttpBodyParam(param: BodyParam) : RxHttpAbstractBodyParam<BodyParam, RxHttpBodyParam>(param) {\n\n                fun setBody(content: String, contentType: MediaType? = null) =\n                    setBody(OkHttpCompat.create(contentType, content))\n\n                fun setBody(content: ByteString, contentType: MediaType? = null) =\n                    setBody(OkHttpCompat.create(contentType, content))\n\n                @JvmOverloads\n                fun setBody(\n                    content: ByteArray,\n                    contentType: MediaType? = null,\n                    offset: Int = 0,\n                    byteCount: Int = content.size,\n                ) = setBody(OkHttpCompat.create(contentType, content, offset, byteCount))\n\n                @JvmOverloads\n                fun setBody(\n                    file: File,\n                    contentType: MediaType? = BuildUtil.getMediaType(file.name),\n                ) = setBody(FileRequestBody(file, 0, contentType))\n                ${isAndroid(\"\"\"\n                @JvmOverloads\n                fun setBody(\n                    context: Context,\n                    uri: Uri,\n                    contentType: MediaType? = BuildUtil.getMediaTypeByUri(context, uri),\n                ) = setBody(UriRequestBody(context, uri, 0, contentType))\n                \"\"\")}\n                //Content-Type: application/json; charset=utf-8\n                fun setBody(any: Any) = apply { param.setBody(any) }\n\n                fun setBody(requestBody: RequestBody) = apply { param.setBody(requestBody) }\n            }\n\n        \"\"\".trimIndent()\n        )\n    }\n\n    private fun generatorRxHttpFormParam(codeGenerator: CodeGenerator) {\n        generatorClass(\n            codeGenerator, \"RxHttpFormParam\", \"\"\"\n            package $rxHttpPackage\n\n            ${isAndroid(\"import android.content.Context\")}\n            ${isAndroid(\"import android.net.Uri\")}\n            ${isAndroid(\"import rxhttp.wrapper.entity.UriRequestBody\")}\n            import okhttp3.Headers\n            import okhttp3.MediaType\n            import okhttp3.MultipartBody\n            import okhttp3.RequestBody\n            import rxhttp.wrapper.OkHttpCompat\n            import rxhttp.wrapper.entity.FileRequestBody\n            import rxhttp.wrapper.entity.UpFile\n            import rxhttp.wrapper.param.FormParam\n            import rxhttp.wrapper.utils.BuildUtil\n            import rxhttp.wrapper.utils.displayName\n            import java.io.File\n\n\n            /**\n             * Github\n             * https://github.com/liujingxing/rxhttp\n             * https://github.com/liujingxing/rxlife\n             * https://github.com/liujingxing/rxhttp/wiki/FAQ\n             * https://github.com/liujingxing/rxhttp/wiki/更新日志\n             */\n            open class RxHttpFormParam(param: FormParam) : RxHttpAbstractBodyParam<FormParam, RxHttpFormParam>(param) {\n                \n                @JvmOverloads\n                fun add(key: String, value: Any?, add: Boolean = true) = apply {\n                    if (add) param.add(key, value)\n                }    \n\n                fun addAll(map: Map<String, *>) = apply { param.addAll(map) }\n\n                fun addEncoded(key: String, value: Any?) = apply { param.addEncoded(key, value) }\n\n                fun addAllEncoded(map: Map<String, *>) = apply { param.addAllEncoded(map) }\n\n                fun removeAllBody() = apply { param.removeAllBody() }\n\n                fun removeAllBody(key: String) = apply { param.removeAllBody(key) }\n\n                fun set(key: String, value: Any?) = apply { param[key] = value }\n\n                fun setEncoded(key: String, value: Any?) = apply { param.setEncoded(key, value) }\n\n                fun addFile(key: String, filePath: String?) = \n                    if (filePath == null) this else addFile(key, File(filePath))\n\n                @JvmOverloads\n                fun addFile(key: String, file: File?, filename: String? = file?.name) =\n                    if (file == null) this else addFile(UpFile(key, file, filename))\n\n                fun addFiles(fileList: List<UpFile>) = apply { fileList.forEach { addFile(it) } }\n\n                fun <T> addFiles(fileMap: Map<String, T>) = apply {\n                    fileMap.forEach { (key, value) -> addFile(key, value) }\n                }\n\n                fun <T> addFiles(key: String, files: List<T>) = apply {\n                    files.forEach { addFile(key, it) }\n                }\n\n                private fun addFile(key: String, file: Any?) {\n                    if (file is File) {\n                        addFile(key, file)\n                    } else if (file is String) {\n                        addFile(key, file)\n                    } else if (file != null) {\n                        throw IllegalArgumentException(\"Incoming data type exception, it must be String or File\")\n                    }\n                }\n\n                fun addFile(upFile: UpFile) = apply {\n                    val requestBody = FileRequestBody(upFile.file, upFile.skipSize, BuildUtil.getMediaType(upFile.filename))\n                    return addFormDataPart(upFile.key, upFile.filename, requestBody)\n                }\n\n                @JvmOverloads\n                fun addPart(\n                    content: ByteArray,\n                    contentType: MediaType? = null,\n                    offset: Int = 0,\n                    byteCount: Int = content.size\n                ) = addPart(OkHttpCompat.create(contentType, content, offset, byteCount))\n                ${isAndroid(\"\"\"\n                @JvmOverloads\n                fun addPart(\n                    context: Context,\n                    uri: Uri,\n                    contentType: MediaType? = BuildUtil.getMediaTypeByUri(context, uri)\n                ) = addPart(UriRequestBody(context, uri, 0, contentType))\n\n                @JvmOverloads\n                fun addPart(\n                    context: Context,\n                    key: String,\n                    uri: Uri,\n                    contentType: MediaType? = BuildUtil.getMediaTypeByUri(context, uri)\n                ) = addPart(context, key, uri.displayName(context), uri, contentType)\n\n                @JvmOverloads\n                fun addPart(\n                    context: Context,\n                    key: String,\n                    filename: String?,\n                    uri: Uri,\n                    contentType: MediaType? = BuildUtil.getMediaTypeByUri(context, uri)\n                ) = addFormDataPart(key, filename, UriRequestBody(context, uri, 0, contentType))\n\n                fun addParts(context: Context, uriMap: Map<String, Uri>) = apply {\n                    uriMap.forEach { (key, value) -> addPart(context, key, value) }\n                }\n\n                fun addParts(context: Context, uris: List<Uri>) = apply {\n                    uris.forEach { addPart(context, it) }\n                }\n\n                fun addParts(context: Context, uris: List<Uri>, contentType: MediaType?) = apply {\n                    uris.forEach { addPart(context, it, contentType) }\n                }\n\n                fun addParts(context: Context, key: String, uris: List<Uri>) = apply {\n                    uris.forEach { addPart(context, key, it) }\n                }\n\n                fun addParts(context: Context, key: String, uris: List<Uri>, contentType: MediaType?) = apply {\n                    uris.forEach { addPart(context, key, it, contentType) }\n                }\n                \"\"\")}\n                fun addPart(requestBody: RequestBody) = addPart(OkHttpCompat.part(requestBody))\n\n                fun addPart(headers: Headers?, requestBody: RequestBody) =\n                    addPart(OkHttpCompat.part(headers, requestBody))\n\n                fun addFormDataPart(\n                    key: String,\n                    fileName: String?,\n                    requestBody: RequestBody\n                ) = addPart(OkHttpCompat.part(key, fileName, requestBody))\n\n                fun addPart(key: String, requestBody: RequestBody) = addFormDataPart(key, null, requestBody)\n                \n                fun addParts(partMap: Map<String, RequestBody>) = apply { \n                    partMap.forEach { (key, value) -> addPart(key, value) } \n                }\n\n                fun addPart(part: MultipartBody.Part) = apply { param.addPart(part) }\n                \n                fun addParts(parts: List<MultipartBody.Part>) = apply { parts.forEach { addPart(it) } }\n\n                //Set content-type to multipart/form-data\n                fun setMultiForm() = setMultiType(MultipartBody.FORM)\n\n                //Set content-type to multipart/mixed\n                fun setMultiMixed() = setMultiType(MultipartBody.MIXED)\n\n                //Set content-type to multipart/alternative\n                fun setMultiAlternative() = setMultiType(MultipartBody.ALTERNATIVE)\n\n                //Set content-type to multipart/digest\n                fun setMultiDigest() = setMultiType(MultipartBody.DIGEST)\n\n                //Set content-type to multipart/parallel\n                fun setMultiParallel() = setMultiType(MultipartBody.PARALLEL)\n\n                //Set the MIME type\n                fun setMultiType(multiType: MediaType?) = apply { param.multiType = multiType }\n            }\n\n        \"\"\".trimIndent()\n        )\n    }\n\n    private fun generatorRxHttpJsonParam(codeGenerator: CodeGenerator) {\n        generatorClass(\n            codeGenerator, \"RxHttpJsonParam\", \"\"\"\n            package $rxHttpPackage\n\n            import com.google.gson.JsonObject\n            \n            import rxhttp.wrapper.param.JsonParam\n            /**\n             * Github\n             * https://github.com/liujingxing/rxhttp\n             * https://github.com/liujingxing/rxlife\n             * https://github.com/liujingxing/rxhttp/wiki/FAQ\n             * https://github.com/liujingxing/rxhttp/wiki/更新日志\n             */\n            open class RxHttpJsonParam(param: JsonParam) : RxHttpAbstractBodyParam<JsonParam, RxHttpJsonParam>(param) {\n            \n                @JvmOverloads\n                fun add(key: String, value: Any?, add: Boolean = true) = apply {\n                    if (add) param.add(key, value)\n                }\n            \n                fun addAll(map: Map<String, *>) = apply { param.addAll(map) }\n            \n                /**\n                 * 将Json对象里面的key-value逐一取出，添加到另一个Json对象中，\n                 * 输入非Json对象将抛出[IllegalStateException]异常\n                 */\n                fun addAll(jsonObject: String) = apply { param.addAll(jsonObject) }\n            \n                /**\n                 * 将Json对象里面的key-value逐一取出，添加到另一个Json对象中\n                 */\n                fun addAll(jsonObject: JsonObject) = apply { param.addAll(jsonObject) }\n            \n                /**\n                 * 添加一个JsonElement对象(Json对象、json数组等)\n                 */\n                fun addJsonElement(key: String, jsonElement: String) = apply {\n                    param.addJsonElement(key, jsonElement)\n                }\n            }\n\n        \"\"\".trimIndent()\n        )\n    }\n\n    private fun generatorRxHttpJsonArrayParam(codeGenerator: CodeGenerator) {\n        generatorClass(\n            codeGenerator, \"RxHttpJsonArrayParam\", \"\"\"\n            package $rxHttpPackage\n\n            import com.google.gson.JsonArray\n            import com.google.gson.JsonObject\n            \n            import rxhttp.wrapper.param.JsonArrayParam\n\n            /**\n             * Github\n             * https://github.com/liujingxing/rxhttp\n             * https://github.com/liujingxing/rxlife\n             * https://github.com/liujingxing/rxhttp/wiki/FAQ\n             * https://github.com/liujingxing/rxhttp/wiki/更新日志\n             */\n            open class RxHttpJsonArrayParam(param: JsonArrayParam) : RxHttpAbstractBodyParam<JsonArrayParam, RxHttpJsonArrayParam>(param) {\n            \n                @JvmOverloads\n                fun add(key: String, value: Any?, add: Boolean = true) = apply {\n                    if (add) param.add(key, value)\n                }\n            \n                fun addAll(map: Map<String, *>) = apply { param.addAll(map) }\n            \n                fun add(any: Any) = apply { param.add(any) }\n            \n                fun addAll(list: List<*>) = apply {  param.addAll(list) }\n            \n                /**\n                 * 添加多个对象，将字符串转JsonElement对象,并根据不同类型,执行不同操作,可输入任意非空字符串\n                 */\n                fun addAll(jsonElement: String) = apply { param.addAll(jsonElement) }\n            \n                fun addAll(jsonArray: JsonArray) = apply { param.addAll(jsonArray) }\n            \n                /**\n                 * 将Json对象里面的key-value逐一取出，添加到Json数组中，成为单独的对象\n                 */\n                fun addAll(jsonObject: JsonObject) = apply { param.addAll(jsonObject) }\n            \n                fun addJsonElement(jsonElement: String) = apply { param.addJsonElement(jsonElement) }\n            \n                /**\n                 * 添加一个JsonElement对象(Json对象、json数组等)\n                 */\n                fun addJsonElement(key: String, jsonElement: String) = apply {\n                    param.addJsonElement(key, jsonElement)\n                }\n            }\n\n        \"\"\".trimIndent()\n        )\n    }\n\n    private fun generatorClass(codeGenerator: CodeGenerator, className: String, content: String) {\n        codeGenerator.createNewFile(\n            Dependencies(false, *emptyList<KSFile>().toTypedArray()),\n            rxHttpPackage,\n            className,\n        ).use {\n            it.write(content.toByteArray())\n        }\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/Ksp.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAnnotationsByType\nimport com.google.devtools.ksp.getClassDeclarationByName\nimport com.google.devtools.ksp.getConstructors\nimport com.google.devtools.ksp.isPublic\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.Dependencies\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.symbol.ClassKind\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSFunctionDeclaration\nimport com.google.devtools.ksp.symbol.KSNode\nimport com.google.devtools.ksp.symbol.KSPropertyDeclaration\nimport com.google.devtools.ksp.symbol.KSType\nimport com.google.devtools.ksp.symbol.KSTypeReference\nimport com.google.devtools.ksp.symbol.KSValueParameter\nimport com.google.devtools.ksp.symbol.Modifier\nimport com.google.devtools.ksp.symbol.Origin\nimport com.rxhttp.compiler.K_ARRAY_TYPE\nimport com.rxhttp.compiler.K_TYPE\nimport com.rxhttp.compiler.isKps2\nimport com.squareup.javapoet.JavaFile\nimport com.squareup.kotlinpoet.ANY\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.BOOLEAN\nimport com.squareup.kotlinpoet.BOOLEAN_ARRAY\nimport com.squareup.kotlinpoet.BYTE\nimport com.squareup.kotlinpoet.BYTE_ARRAY\nimport com.squareup.kotlinpoet.CHAR\nimport com.squareup.kotlinpoet.CHAR_ARRAY\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.DOUBLE\nimport com.squareup.kotlinpoet.DOUBLE_ARRAY\nimport com.squareup.kotlinpoet.FLOAT\nimport com.squareup.kotlinpoet.FLOAT_ARRAY\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.INT\nimport com.squareup.kotlinpoet.INT_ARRAY\nimport com.squareup.kotlinpoet.KModifier\nimport com.squareup.kotlinpoet.LONG\nimport com.squareup.kotlinpoet.LONG_ARRAY\nimport com.squareup.kotlinpoet.MemberName\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.ParameterizedTypeName\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.SHORT\nimport com.squareup.kotlinpoet.SHORT_ARRAY\nimport com.squareup.kotlinpoet.TypeName\nimport com.squareup.kotlinpoet.TypeVariableName\nimport com.squareup.kotlinpoet.U_BYTE_ARRAY\nimport com.squareup.kotlinpoet.U_INT_ARRAY\nimport com.squareup.kotlinpoet.U_LONG_ARRAY\nimport com.squareup.kotlinpoet.U_SHORT_ARRAY\nimport com.squareup.kotlinpoet.ksp.TypeParameterResolver\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.toTypeName\nimport com.squareup.kotlinpoet.ksp.toTypeParameterResolver\nimport org.jetbrains.annotations.Nullable\n\n/**\n * User: ljx\n * Date: 2021/11/11\n * Time: 17:38\n */\n\n//获取对象类型，包名+类名\ninternal fun KSTypeReference.getQualifiedName() =\n    resolve().declaration.qualifiedName?.asString()\n\n//获取方法名\ninternal fun KSFunctionDeclaration.getFunName() = simpleName.asString()\n\n\n/*\n\n| 参数类型             | kapt (JTypeName -> KTypeName)                        | ksp (KTypeName)\n| :---:               |  :---:                                               |   :---:\n|  `int... a`         |  int[] -> kotlin.IntArray                            |   kotlin.IntArray & vararg\n|  `String... a`      |  java.lang.String[] -> kotlin.Array<kotlin.String>   |   kotlin.Array<kotlin.String> & vararg\n|  `int[]  a`         |  int[] -> kotlin.IntArray                            |   kotlin.IntArray\n|  `String[] a`       |  java.lang.String[] -> kotlin.Array<kotlin.String>   |   kotlin.Array<kotlin.String>\n|  `vararg a: Int`    |  int[] -> kotlin.IntArray                            |   kotlin.Int & vararg\n|  `vararg a: String` |  java.lang.String[] -> kotlin.Array<kotlin.String>   |   kotlin.String & vararg\n|  `a: IntArray`      |  int[] -> kotlin.IntArray                            |   kotlin.IntArray\n|  `a: Array<String>` |  java.lang.String[] -> kotlin.Array<kotlin.String>   |   kotlin.Array<kotlin.String>\n\n */\n@KspExperimental\ninternal fun KSValueParameter.toKParameterSpec(\n    typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY\n): ParameterSpec {\n    val variableName = name!!.asString()\n    val isNullable = getAnnotationsByType(Nullable::class).firstOrNull() != null\n    var typeName = type.toTypeName(typeParamResolver)\n    if (isVararg && isJava() && !isKps2) {\n        typeName = when (typeName) {\n            BOOLEAN_ARRAY -> BOOLEAN\n            BYTE_ARRAY, U_BYTE_ARRAY -> BYTE\n            CHAR_ARRAY -> CHAR\n            SHORT_ARRAY, U_SHORT_ARRAY -> SHORT\n            INT_ARRAY, U_INT_ARRAY -> INT\n            LONG_ARRAY, U_LONG_ARRAY -> LONG\n            FLOAT_ARRAY -> FLOAT\n            DOUBLE_ARRAY -> DOUBLE\n            is ParameterizedTypeName -> typeName.typeArguments.first()\n            else -> typeName\n        }\n    }\n    if (isNullable) typeName = typeName.copy(true)\n    return ParameterSpec.builder(variableName, typeName).apply {\n        if (isVararg)\n            addModifiers(KModifier.VARARG)\n        if (isNoInline)\n            addModifiers(KModifier.NOINLINE)\n        if (isCrossInline)\n            addModifiers(KModifier.CROSSINLINE)\n    }.build()\n}\n\ninternal fun ClassName.parameterizedBy(vararg s: String) =\n    parameterizedBy(s.map { TypeVariableName(it) })\n\ninternal fun KSNode.isJava() = origin == Origin.JAVA || origin == Origin.JAVA_LIB\n\ninternal fun KSNode.isKotlin() = origin == Origin.KOTLIN || origin == Origin.KOTLIN_LIB\n\ninternal fun KSClassDeclaration.superclass(): KSTypeReference? {\n    return superTypes.find {\n        val declaration = it.resolve().declaration\n        (declaration as? KSClassDeclaration)?.classKind == ClassKind.CLASS\n    }\n}\n\n//判断解析器构造方法是否有效\nfun KSFunctionDeclaration.isValid(typeCount: Int): Boolean {\n    //1、非public方法，无效\n    if (!isPublic()) return false\n    val parameters = parameters\n    if (parameters.isEmpty()) {\n        //2、构造方法没有参数，且泛型数量等于0，有效，反之无效\n        return typeCount == 0\n    }\n    val firstParameter = parameters.first()\n    val firstParameterType = firstParameter.type.toTypeName()\n    if (firstParameterType == K_ARRAY_TYPE || (firstParameterType == K_TYPE && firstParameter.isVararg)) {\n        //3、第一个参数为Type类型数组 或 Type类型可变参数, 有效\n        return true\n    }\n    //4、构造方法参数数量小于泛型数量，无效\n    if (parameters.size < typeCount) return false\n    //5、构造方法前n个参数，皆为Type类型，有效  n为泛型数量\n    return parameters.take(typeCount).all { K_TYPE == it.type.toTypeName() }\n}\n\n//获取onParser方法返回类型\nfun KSClassDeclaration.findOnParserFunReturnType(): TypeName? {\n    val ksFunction = getAllFunctions().find {\n        it.isPublic() &&\n                !it.modifiers.contains(Modifier.JAVA_STATIC) &&\n                it.getFunName() == \"onParse\" &&\n                it.parameters.size == 1 &&\n                it.parameters[0].type.getQualifiedName() == \"okhttp3.Response\"\n    }\n    return ksFunction?.returnType?.toTypeName(typeParameters.toTypeParameterResolver())\n}\n\n/**\n * 获取Parser接口实际泛型类型\n * @param typeNameMap  key 类自身声明的泛型名称，如XxxParser<A, B>  value 子类(实现类)传递的真实泛型类型, 如 AParser<A> : XxxParser<User<A>, Book>\n */\nfun KSClassDeclaration.getParserTypeParam(typeNameMap: Map<String, TypeName>? = null): TypeName? {\n    val className = qualifiedName?.asString()\n    val parserName = \"rxhttp.wrapper.parse.Parser\"\n    val typeParserName = \"rxhttp.wrapper.parse.TypeParser\"\n    val typeParameters = typeParameters\n    if (parserName == className || typeParserName == className) {\n        return typeNameMap?.get(typeParameters.first().name.asString())\n    }\n    val typeParamResolver = typeParameters.toTypeParameterResolver()\n    for (superType in superTypes) {  //superTypes 包含父类及直接实现的接口\n        val typeName = superType.toTypeName(typeParamResolver)  //引用的父类类型\n        if (typeName == ANY) continue\n        val ksDeclaration = superType.resolve().declaration  //声明的父类类型\n        if (typeName is ParameterizedTypeName) {\n            //将泛型类型转换为子类传递的类型\n            val typeNames = typeName.typeArguments.map { it.convert(typeNameMap) }\n            val ksTypeParameters = ksDeclaration.typeParameters //获取类自身声明的泛型列表\n            var i = 0\n            val newTypeNameMap = ksTypeParameters.associate {\n                it.name.asString() to typeNames[i++]\n            }\n            val answer = (ksDeclaration as KSClassDeclaration).getParserTypeParam(newTypeNameMap)\n            if (answer != null) {\n                return answer\n            }\n        } else {\n            (ksDeclaration as KSClassDeclaration).getParserTypeParam()\n        }\n    }\n    return null\n}\n\nfun TypeName.convert(typeNameMap: Map<String, TypeName>? = null): TypeName {\n    if (typeNameMap == null) return this\n    return when (this) {\n        is TypeVariableName -> {\n            val typeName = typeNameMap[name]\n            return typeName?.copy(typeName.isNullable || isNullable) ?: this\n        }\n\n        is ParameterizedTypeName -> {\n            rawType.parameterizedBy(typeArguments.map { it.convert(typeNameMap) })\n                .copy(isNullable)\n        }\n\n        else -> this\n    }\n}\n\nfun ParameterSpec.isVararg() = modifiers.contains(KModifier.VARARG)\n\nfun KSClassDeclaration.getPublicConstructors() = getConstructors().filter { it.isPublic() }\n\ninternal fun KSType.instanceOf(className: String, resolver: Resolver): Boolean {\n    val ksClass = resolver.getClassDeclarationByName(className) ?: return false\n    return ksClass.asStarProjectedType().isAssignableFrom(this)\n}\n\n@KspExperimental\ninternal fun KSPropertyDeclaration.isStaticToJava(): Boolean {\n    return getAnnotationsByType(JvmField::class).firstOrNull() != null\n            || Modifier.CONST in modifiers\n}\n\ninternal fun FunSpec.Builder.addParameter(\n    name: String,\n    typeName: TypeName,\n    nullable: Boolean = false,\n    vararg modifiers: KModifier\n) = addParameter(name, typeName.copy(nullable), *modifiers)\n\nfun newParameterSpec(\n    name: String,\n    typeName: TypeName,\n    nullable: Boolean = false,\n    vararg modifiers: KModifier\n) = ParameterSpec.builder(name, typeName.copy(nullable), *modifiers).build()\n\ninternal fun KSPropertyDeclaration.toMemberName(): MemberName {\n    val className = (parent as? KSClassDeclaration)?.toClassName()\n    val fieldName = simpleName.asString()\n    return if (className != null) {\n        MemberName(className, fieldName)\n    } else {\n        //kotlin top property\n        MemberName(packageName.asString(), fieldName)\n    }\n}\n\ninternal fun KSPLogger.error(throwable: Throwable, ksNode: KSNode) {\n    error(throwable.message ?: \"\", ksNode)\n}\n\n\ninternal fun String.firstLetterUpperCase(): String {\n    val charArray = toCharArray()\n    val firstChar = charArray.firstOrNull() ?: return this\n    if (firstChar.code in 97..122) {\n        charArray[0] = firstChar.minus(32)\n    }\n    return String(charArray)\n}\n\ninternal fun JavaFile.writeTo(\n    codeGenerator: CodeGenerator,\n    dependencies: Dependencies = Dependencies(false)\n) {\n    val fos = codeGenerator.createNewFile(dependencies, packageName, typeSpec.name, \"java\")\n    fos.bufferedWriter(Charsets.UTF_8).use(this::writeTo)\n}\n\ninternal fun getJvmName(name: String): AnnotationSpec {\n    return AnnotationSpec.builder(JvmName::class)\n        .addMember(\"\\\"$name\\\"\")\n        .useSiteTarget(AnnotationSpec.UseSiteTarget.GET)\n        .build()\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/OkClientVisitor.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAnnotationsByType\nimport com.google.devtools.ksp.isPublic\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.symbol.ClassKind\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.google.devtools.ksp.symbol.KSPropertyDeclaration\nimport com.google.devtools.ksp.symbol.KSVisitorVoid\nimport com.google.devtools.ksp.symbol.Modifier\nimport com.rxhttp.compiler.rxhttpKClass\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.TypeVariableName\nimport rxhttp.wrapper.annotation.OkClient\nimport java.util.*\n\nclass OkClientVisitor(\n    private val resolver: Resolver,\n    private val logger: KSPLogger\n) : KSVisitorVoid() {\n\n    private val elementMap = LinkedHashMap<String, KSPropertyDeclaration>()\n\n    val originatingKSFiles: List<KSFile>\n        get() = elementMap.values.map { it.containingFile!! }\n\n    @KspExperimental\n    override fun visitPropertyDeclaration(property: KSPropertyDeclaration, data: Unit) {\n        try {\n            property.checkOkClientProperty(resolver)\n            val annotation = property.getAnnotationsByType(OkClient::class).firstOrNull()\n            var name = annotation?.name\n            if (name.isNullOrBlank()) {\n                name = property.simpleName.asString().firstLetterUpperCase()\n            }\n            if (elementMap.containsKey(name)) {\n                val msg =\n                    \"The variable '${property.simpleName.asString()}' in the @OkClient annotation 'name = $name' is duplicated\"\n                throw NoSuchElementException(msg)\n            }\n            elementMap[name] = property\n        } catch (e: NoSuchElementException) {\n            logger.error(e, property)\n        }\n    }\n\n    fun getFunList(): List<FunSpec> {\n        val typeVariableR = TypeVariableName(\"R\", rxhttpKClass.parameterizedBy(\"P\", \"R\")) //泛型R\n        return elementMap.mapNotNull { entry ->\n            val key = entry.key\n            val ksProperty = entry.value\n            val memberName = ksProperty.toMemberName()\n            FunSpec.builder(\"set$key\")\n                .addStatement(\"return setOkClient(%M)\", memberName)\n                .returns(typeVariableR)\n                .build()\n        }\n    }\n}\n\n@Throws(NoSuchElementException::class)\nprivate fun KSPropertyDeclaration.checkOkClientProperty(resolver: Resolver) {\n    val variableName = simpleName.asString()\n\n    val className = \"okhttp3.OkHttpClient\"\n    if (!type.resolve().instanceOf(className, resolver)) {\n        throw NoSuchElementException(\"The variable '$variableName' must be OkHttpClient\")\n    }\n\n    var curParent = parent\n    while (curParent is KSClassDeclaration) {\n        if (!curParent.isPublic()) {\n            val msg = \"The class '${curParent.qualifiedName?.asString()}' must be public\"\n            throw NoSuchElementException(msg)\n        }\n        curParent = curParent.parent\n    }\n\n    if (!isPublic()) {\n        throw NoSuchElementException(\"The variable '$variableName' must be public\")\n    }\n\n    if (isJava() && Modifier.JAVA_STATIC !in modifiers) {\n        throw NoSuchElementException(\"The variable '$variableName' must be static\")\n    }\n\n    if (isKotlin()) {\n        val parent = parent\n        //在kt文件里，说明是顶级变量，属于合法，直接返回\n        if (parent is KSFile) return\n        //在伴生对象里面，是合法的，直接返回\n        if ((parent as? KSClassDeclaration)?.isCompanionObject == true) return\n\n        if ((parent as? KSClassDeclaration)?.classKind != ClassKind.OBJECT) {\n            //必需要声明在object对象里\n            throw NoSuchElementException(\"The variable '$variableName' must be declared in the object\")\n        }\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/ParamsVisitor.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAnnotationsByType\nimport com.google.devtools.ksp.getDeclaredFunctions\nimport com.google.devtools.ksp.isAbstract\nimport com.google.devtools.ksp.isConstructor\nimport com.google.devtools.ksp.isPublic\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.google.devtools.ksp.symbol.KSVisitorVoid\nimport com.google.devtools.ksp.symbol.Modifier\nimport com.rxhttp.compiler.common.joinToStringIndexed\nimport com.rxhttp.compiler.common.toParamNames\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpKClass\nimport com.squareup.kotlinpoet.ANY\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.KModifier\nimport com.squareup.kotlinpoet.ParameterizedTypeName\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.STRING\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.UNIT\nimport com.squareup.kotlinpoet.jvm.jvmStatic\nimport com.squareup.kotlinpoet.jvm.throws\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.toKModifier\nimport com.squareup.kotlinpoet.ksp.toTypeName\nimport com.squareup.kotlinpoet.ksp.toTypeParameterResolver\nimport com.squareup.kotlinpoet.ksp.toTypeVariableName\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport rxhttp.wrapper.annotation.Param\nimport java.io.IOException\n\nclass ParamsVisitor(\n    private val logger: KSPLogger,\n    private val resolver: Resolver\n) : KSVisitorVoid() {\n\n    private val ksClassMap = LinkedHashMap<String, KSClassDeclaration>()\n\n    val originatingKSFiles: List<KSFile>\n        get() = ksClassMap.values.map { it.containingFile!! }\n\n    @KspExperimental\n    override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {\n        try {\n            classDeclaration.checkParamsValidClass(resolver)\n            val annotations = classDeclaration.getAnnotationsByType(Param::class)\n            val name = annotations.firstOrNull()?.methodName\n            if (name.isNullOrBlank()) {\n                val msg = \"methodName() in @${Param::class.java.simpleName} for class \" +\n                        \"'${classDeclaration.qualifiedName?.asString()}' is null or empty! that's not allowed\"\n                throw NoSuchElementException(msg)\n            }\n            ksClassMap[name] = classDeclaration\n        } catch (e: NoSuchElementException) {\n            logger.error(e, classDeclaration)\n        }\n    }\n\n    @KspExperimental\n    @Throws(IOException::class)\n    fun getFunList(codeGenerator: CodeGenerator): List<FunSpec> {\n        val funList = ArrayList<FunSpec>()\n        var funSpecBuilder: FunSpec.Builder\n        ksClassMap.forEach { (key, ksClass) ->\n            val rxHttpTypeNames = ksClass.typeParameters.map { it.toTypeVariableName() }\n            val param = ksClass.toClassName()\n            val rxHttpName = \"RxHttp${ksClass.simpleName.asString()}\"\n            val rxHttpParamName = rxhttpKClass.peerClass(rxHttpName)\n            val methodReturnType = if (rxHttpTypeNames.isNotEmpty()) {\n                rxHttpParamName.parameterizedBy(*rxHttpTypeNames.toTypedArray())\n            } else {\n                rxHttpParamName\n            }\n\n            val classTypeParams = ksClass.typeParameters.toTypeParameterResolver()\n            //遍历public构造方法\n            ksClass.getPublicConstructors().forEach { function ->\n                val functionTypeParams = function.typeParameters\n                    .toTypeParameterResolver(classTypeParams)\n                //构造方法参数\n                val parameterSpecs = function.parameters\n                    .mapTo(ArrayList()) { it.toKParameterSpec(functionTypeParams) }\n                val prefix = \"return %T(%T(\"\n                val postfix = \"))\"\n                val methodBody = parameterSpecs\n                    .joinToStringIndexed(\", \", prefix, postfix) { index, it ->\n                        if (index == 0 && it.type == STRING) {\n                            \"format(${it.name}, *formatArgs)\"\n                        } else if (it.isVararg()) \"*${it.name}\" else it.name\n                    }\n                if (parameterSpecs.firstOrNull()?.type == STRING) {\n                    parameterSpecs.add(newParameterSpec(\"formatArgs\", ANY, true, KModifier.VARARG))\n                }\n                FunSpec.builder(key)\n                    .jvmStatic()\n                    .addParameters(parameterSpecs)\n                    .addTypeVariables(rxHttpTypeNames)\n                    .addStatement(methodBody, rxHttpParamName, param)\n                    .returns(methodReturnType)\n                    .build()\n                    .apply { funList.add(this) }\n            }\n            val superclass = ksClass.superclass()\n            var prefix = \"(param as ${ksClass.simpleName.asString()}).\"\n            val rxHttpParam = when (superclass?.getQualifiedName()) {\n                \"rxhttp.wrapper.param.BodyParam\" -> rxhttpKClass.peerClass(\"RxHttpBodyParam\")\n                \"rxhttp.wrapper.param.FormParam\" -> rxhttpKClass.peerClass(\"RxHttpFormParam\")\n                \"rxhttp.wrapper.param.JsonParam\" -> rxhttpKClass.peerClass(\"RxHttpJsonParam\")\n                \"rxhttp.wrapper.param.JsonArrayParam\" -> rxhttpKClass.peerClass(\"RxHttpJsonArrayParam\")\n                \"rxhttp.wrapper.param.NoBodyParam\" -> rxhttpKClass.peerClass(\"RxHttpNoBodyParam\")\n                else -> {\n                    val typeName = superclass?.toTypeName()\n                    if ((typeName as? ParameterizedTypeName)?.rawType?.toString() == \"rxhttp.wrapper.param.AbstractBodyParam\") {\n                        prefix = \"param.\"\n                        rxhttpKClass.peerClass(\"RxHttpAbstractBodyParam\")\n                            .parameterizedBy(param, rxHttpParamName)\n                    } else {\n                        prefix = \"param.\"\n                        rxhttpKClass.parameterizedBy(param, rxHttpParamName)\n                    }\n                }\n            }\n            val rxHttpPostCustomFun = ArrayList<FunSpec>()\n            FunSpec.constructorBuilder()\n                .addParameter(\"param\", param)\n                .callSuperConstructor(\"param\")\n                .build()\n                .apply { rxHttpPostCustomFun.add(this) }\n\n            ksClass.getDeclaredFunctions().filter {\n                it.isPublic() && !it.isConstructor() &&\n                        Modifier.OVERRIDE !in it.modifiers &&\n                        it.getAnnotationsByType(Override::class).firstOrNull() == null\n            }.forEach { ksFunction ->\n                val returnType = ksFunction.returnType?.toTypeName().let {\n                    if (it == param) rxHttpParamName else it\n                }\n\n                val functionTypeParams = ksFunction.typeParameters\n                    .toTypeParameterResolver(classTypeParams)\n                //方法参数\n                val parameterSpecs = ksFunction.parameters.map { ksValueParameter ->\n                    ksValueParameter.toKParameterSpec(functionTypeParams)\n                }\n                //方法参数名字\n                val paramNames = parameterSpecs.toParamNames()\n                //方法体\n                val methodBody = \"${ksFunction.simpleName.asString()}($paramNames)\"\n                val typeVariableNames = ksFunction.typeParameters.map { it.toTypeVariableName() }\n\n                val throwTypeNames = resolver.getJvmCheckedException(ksFunction)\n                    .map { it.toClassName() }.toList()\n\n                val modifiers = ksFunction.modifiers.mapNotNull { it.toKModifier() }\n\n                funSpecBuilder = FunSpec.builder(ksFunction.simpleName.asString())\n                    .addModifiers(modifiers)\n                    .addTypeVariables(typeVariableNames)\n                    .addParameters(parameterSpecs)\n\n                if (throwTypeNames.isNotEmpty()) {\n                    funSpecBuilder.throws(throwTypeNames)\n                }\n                when {\n                    returnType === rxHttpParamName -> {\n                        funSpecBuilder.addCode(\"\"\"\n                            return apply {\n                              $prefix$methodBody \n                            }\n                        \"\"\".trimIndent(), param)\n                    }\n                    returnType == UNIT -> {\n                        funSpecBuilder.addStatement(prefix + methodBody)\n                    }\n                    else -> {\n                        funSpecBuilder.addStatement(\"return $prefix$methodBody\", param)\n                    }\n                }\n                returnType?.apply { funSpecBuilder.returns(this) }\n\n                rxHttpPostCustomFun.add(funSpecBuilder.build())\n            }\n            val rxHttpPostEncryptFormParamSpec = TypeSpec.classBuilder(rxHttpName)\n                .addKdoc(\n                    \"\"\"\n                    Github\n                    https://github.com/liujingxing/rxhttp\n                    https://github.com/liujingxing/rxlife\n                \"\"\".trimIndent()\n                )\n                .addOriginatingKSFile(ksClass.containingFile!!)\n                .addTypeVariables(rxHttpTypeNames)\n                .superclass(rxHttpParam)\n                .addFunctions(rxHttpPostCustomFun)\n                .build()\n\n            FileSpec.builder(rxHttpPackage, rxHttpName)\n                .addType(rxHttpPostEncryptFormParamSpec)\n                .build()\n                .writeTo(codeGenerator, false)\n        }\n        return funList\n    }\n}\n\n\n@Throws(NoSuchElementException::class)\nprivate fun KSClassDeclaration.checkParamsValidClass(resolver: Resolver) {\n    val paramSimpleName = Param::class.java.simpleName\n    val elementQualifiedName = qualifiedName?.asString()\n    if (!isPublic()) {\n        throw NoSuchElementException(\"The class '$elementQualifiedName' must be public\")\n    }\n    if (isAbstract()) {\n        val msg =\n            \"The class '$elementQualifiedName' is abstract. You can't annotate abstract classes with @$paramSimpleName\"\n        throw NoSuchElementException(msg)\n    }\n\n    val className = \"rxhttp.wrapper.param.Param\"\n    if (!asStarProjectedType().instanceOf(className, resolver)) {\n        val msg =\n            \"The class '$elementQualifiedName' annotated with @$paramSimpleName must inherit from $className\"\n        throw NoSuchElementException(msg)\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/ParserVisitor.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.KSTypesNotPresentException\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAnnotationsByType\nimport com.google.devtools.ksp.getConstructors\nimport com.google.devtools.ksp.isAbstract\nimport com.google.devtools.ksp.isPublic\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.google.devtools.ksp.symbol.KSVisitorVoid\nimport com.rxhttp.compiler.K_TYPE\nimport com.rxhttp.compiler.common.flapTypeParameterSpecTypes\nimport com.rxhttp.compiler.common.getTypeVariableString\nimport com.rxhttp.compiler.common.isArrayType\nimport com.rxhttp.compiler.common.joinToStringIndexed\nimport com.rxhttp.compiler.common.toParamNames\nimport com.rxhttp.compiler.isDependenceRxJava\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpKClass\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.CodeBlock\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.LIST\nimport com.squareup.kotlinpoet.MemberName\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.ParameterizedTypeName\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.TypeName\nimport com.squareup.kotlinpoet.TypeVariableName\nimport com.squareup.kotlinpoet.asClassName\nimport com.squareup.kotlinpoet.javapoet.JClassName\nimport com.squareup.kotlinpoet.javapoet.KotlinPoetJavaPoetPreview\nimport com.squareup.kotlinpoet.javapoet.toKClassName\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.toTypeParameterResolver\nimport com.squareup.kotlinpoet.ksp.toTypeVariableName\nimport rxhttp.wrapper.annotation.Parser\nimport java.util.*\n\n/**\n * User: ljx\n * Date: 2021/10/17\n * Time: 22:33\n */\nclass ParserVisitor(\n    private val resolver: Resolver,\n    private val logger: KSPLogger\n) : KSVisitorVoid() {\n\n    private val ksClassMap = LinkedHashMap<String, KSClassDeclaration>()\n    private val classNameMap = LinkedHashMap<String, List<ClassName>>()\n\n    val originatingKSFiles: List<KSFile>\n        get() = ksClassMap.values.map { it.containingFile!! }\n\n    @OptIn(KotlinPoetJavaPoetPreview::class)\n    @KspExperimental\n    override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {\n        try {\n            classDeclaration.checkParserValidClass(resolver)\n            val annotation = classDeclaration.getAnnotationsByType(Parser::class).firstOrNull()\n            var name = annotation?.name\n            if (name.isNullOrBlank()) {\n                name = classDeclaration.simpleName.toString()\n            }\n            ksClassMap[name] = classDeclaration\n            val classNames =\n                try {\n                    annotation?.wrappers?.map { JClassName.get(it.java).toKClassName() }\n                } catch (e: KSTypesNotPresentException) {\n                    e.ksTypes.map {\n                        val className = it.declaration.qualifiedName?.asString().toString()\n                        //fix https://github.com/liujingxing/rxhttp/issues/476\n                        JClassName.bestGuess(className).toKClassName()\n                    }\n                }\n            classNames?.let { classNameMap[name] = it }\n\n        } catch (e: NoSuchElementException) {\n            logger.error(e, classDeclaration)\n        }\n    }\n\n    @KspExperimental\n    fun getFunList(\n        codeGenerator: CodeGenerator,\n        companionFunList: MutableList<FunSpec>,\n        defaultKsFile: KSFile?\n    ): List<FunSpec> {\n        val funList = ArrayList<FunSpec>()\n        val rxHttpExtensions = RxHttpExtensions(logger)\n        //遍历自定义解析器\n        ksClassMap.forEach { (parserAlias, ksClass) ->\n            //生成kotlin编写的toObservableXxx/toAwaitXxx/toFlowXxx方法\n            rxHttpExtensions.generateRxHttpExtendFun(ksClass, parserAlias)\n            //生成Java环境下toObservableXxx方法\n            val toObservableXxxFunList = ksClass\n                .getToObservableXxxFun(parserAlias, classNameMap, companionFunList)\n            funList.addAll(toObservableXxxFunList)\n        }\n        rxHttpExtensions.generateClassFile(codeGenerator, defaultKsFile)\n        return funList\n    }\n}\n\n@KspExperimental\nprivate fun KSClassDeclaration.getToObservableXxxFun(\n    parserAlias: String,\n    typeMap: LinkedHashMap<String, List<ClassName>>,\n    companionFunList: MutableList<FunSpec>,\n): List<FunSpec> {\n    val funList = arrayListOf<FunSpec>()\n    //onParser方法返回类型\n    val onParserFunReturnType = getParserTypeParam() ?: return emptyList()\n    val typeVariableNames = typeParameters.map { it.toTypeVariableName() }\n    val typeCount = typeVariableNames.size  //泛型数量\n    val customParser = toClassName()\n\n    //遍历构造方法\n    for (constructor in getConstructors()) {\n        if (!constructor.isValid(typeCount)) continue\n        val classTypeParams = typeParameters.toTypeParameterResolver()\n        val functionTypeParams =\n            constructor.typeParameters.toTypeParameterResolver(classTypeParams)\n        //原始参数\n        val originParameterSpecs = constructor.parameters.map {\n            it.toKParameterSpec(functionTypeParams)\n        }\n        //将原始参数里的第一个Type数组或Type类型可变参数转换为n个Type类型参数，n为泛型数量\n        val typeParameterSpecs = originParameterSpecs.flapTypeParameterSpecTypes(typeVariableNames)\n        //将Type类型参数转换为Class<T>类型参数，有泛型时才转\n        val classParameterSpecs = typeParameterSpecs.typeToClassParameterSpecs(typeVariableNames)\n\n        //方法名\n        val funName = \"toObservable$parserAlias\"\n        //返回类型(Observable<T>类型)\n        val toObservableXxxFunReturnType = rxhttpKClass.peerClass(\"ObservableCall\")\n            .parameterizedBy(onParserFunReturnType)\n\n        val wrapCustomParser = MemberName(rxHttpPackage, \"wrap${customParser.simpleName}\")\n        val types = typeVariableNames.getTypeVariableString() // <T>, <K, V> 等\n\n        //方法体\n        val toObservableXxxFunBody =\n            if (typeCount == 1 && onParserFunReturnType is TypeVariableName) {\n                val paramNames = typeParameterSpecs.toParamNames()\n                CodeBlock.of(\"return toObservable(%M($paramNames))\", wrapCustomParser)\n            } else {\n                val paramNames = typeParameterSpecs.toParamNames(originParameterSpecs, typeCount)\n                CodeBlock.of(\"return toObservable(%T$types($paramNames))\", customParser)\n            }\n\n        if (isDependenceRxJava()) {\n            //生成toObservableXxx(Type)方法\n            FunSpec.builder(funName)\n                .addOriginatingKSFile(containingFile!!)\n                .addTypeVariables(typeVariableNames)\n                .addParameters(typeParameterSpecs)\n                .addCode(toObservableXxxFunBody)\n                .returns(toObservableXxxFunReturnType)\n                .build()\n                .apply { funList.add(this) }\n        }\n\n        if (typeCount == 1 && onParserFunReturnType is TypeVariableName) {\n            val t = TypeVariableName(\"T\")\n            val typeUtil = ClassName(\"rxhttp.wrapper.utils\", \"TypeUtil\")\n            val okResponseParser = ClassName(\"rxhttp.wrapper.parse\", \"OkResponseParser\")\n            val parserClass = okResponseParser.peerClass(\"Parser\").parameterizedBy(t)\n\n            val suppressAnnotation = AnnotationSpec.builder(Suppress::class)\n                .addMember(\"%S\", \"UNCHECKED_CAST\")\n                .build()\n\n            val firstParamName = typeParameterSpecs.first().name\n            val paramNames = typeParameterSpecs.toParamNames(originParameterSpecs, typeCount)\n                .replace(firstParamName, \"actualType\")\n\n            FunSpec.builder(\"wrap${customParser.simpleName}\")\n                .addOriginatingKSFile(containingFile!!)\n                .addAnnotation(suppressAnnotation)\n                .addTypeVariable(t)\n                .addParameters(typeParameterSpecs)\n                .returns(parserClass)\n                .addCode(\n                    \"\"\"\n                    val actualType = %T.getActualType($firstParamName) ?: $firstParamName\n                    val parser = %T<Any>($paramNames)\n                    val actualParser = if (actualType == $firstParamName) parser else %T(parser)\n                    return actualParser as Parser<T>\n                \"\"\".trimIndent(), typeUtil, customParser, okResponseParser\n                )\n                .build()\n                .apply { companionFunList.add(this) }\n        }\n\n        if (typeCount > 0 && isDependenceRxJava()) {\n            val paramNames = classParameterSpecs.toParamNames(typeCount)\n            val typeOfs = typeVariableNames.getTypeVariableString()\n\n            //生成toObservableXxx(Class<T>)方法\n            val funSpec = FunSpec.builder(funName)\n                .addOriginatingKSFile(containingFile!!)\n                .addTypeVariables(typeVariableNames)\n                .addParameters(classParameterSpecs)\n                .addStatement(\"return $funName$typeOfs($paramNames)\")  //方法里面的表达式\n                .returns(toObservableXxxFunReturnType)\n                .build()\n                .apply { funList.add(this) }\n\n            //过滤出非Any类型边界\n            val nonAnyBounds = typeVariableNames.first().bounds.filter { typeName ->\n                val name = typeName.toString()\n                name != \"kotlin.Any\" && name != \"kotlin.Any?\"\n            }\n            /**\n             * 生成Parser注解里wrappers字段对应的toObservableXxx方法，如满足以下3个条件\n             * 1、泛型数量为1\n             * 2、泛型没有边界(Any类型边界除外)\n             * 3、解析器onParse方法返回泛型\n             */\n            if (typeCount == 1 && nonAnyBounds.isEmpty() && onParserFunReturnType is TypeVariableName) {\n                val toObservableXxxFunList = funSpec\n                    .getToObservableXxxWrapFun(parserAlias, onParserFunReturnType, typeMap)\n                funList.addAll(toObservableXxxFunList)\n            }\n        }\n    }\n    return funList\n}\n\n/**\n * 生成Parser注解里wrappers字段指定类对应的toObservableXxx方法\n * @param parserAlias 解析器别名\n * @param onParserFunReturnType 解析器里onParser方法的返回类型\n * @param typeMap Parser注解里wrappers字段集合\n */\nprivate fun FunSpec.getToObservableXxxWrapFun(\n    parserAlias: String,\n    onParserFunReturnType: TypeName,\n    typeMap: LinkedHashMap<String, List<ClassName>>,\n): List<FunSpec> {\n    val funSpec = this  //解析器对应的toObservableXxx方法，没有经过wrappers字段包裹前的\n    val funList = mutableListOf<FunSpec>()\n    val parameterSpecs = funSpec.parameters\n    val typeVariableNames = funSpec.typeVariables\n    val typeCount = typeVariableNames.size\n\n    val wrapperListClass = arrayListOf<ClassName>()\n    typeMap[parserAlias]?.apply { wrapperListClass.addAll(this) }\n    if (LIST !in wrapperListClass) {\n        wrapperListClass.add(0, LIST)\n    }\n    wrapperListClass.forEach { wrapperClass ->\n\n        //1、toObservableXxx方法返回值\n        val onParserFunReturnWrapperType =\n            if (onParserFunReturnType is ParameterizedTypeName) { // List<T>, Map<K,V>等包含泛型的类\n                //返回类型有n个泛型，需要对每个泛型再次包装\n                val typeNames = onParserFunReturnType.typeArguments.map { typeArg ->\n                    wrapperClass.parameterizedBy(typeArg)\n                }\n                onParserFunReturnType.rawType.parameterizedBy(*typeNames.toTypedArray())\n            } else {\n                wrapperClass.parameterizedBy(onParserFunReturnType.copy(false))\n            }\n        val toFunReturnType = rxhttpKClass.peerClass(\"ObservableCall\")\n            .parameterizedBy(onParserFunReturnWrapperType.copy(onParserFunReturnType.isNullable))\n\n        //2、toObservableXxx方法名\n        val name = wrapperClass.toString()\n        val simpleName = name.substring(name.lastIndexOf(\".\") + 1)\n        val funName = \"toObservable$parserAlias${simpleName}\"\n\n        //3、toObservableXxx方法体\n        val funBody = CodeBlock.builder()\n        val paramNames = parameterSpecs.joinToStringIndexed { index, it ->\n            if (index < typeCount) {\n                //Class类型参数，需要进行再次包装，最后再取参数名\n                val variableName = \"${it.name}$simpleName\"\n                //格式：val tTypeList = List::class.parameterizedBy(tType)\n                val expression =\n                    \"val $variableName = $simpleName::class.parameterizedBy(${it.name})\"\n                funBody.addStatement(expression)\n                variableName\n            } else if (it.isVararg()) \"*${it.name}\" else it.name\n        }\n        funBody.addStatement(\"return ${funSpec.name}($paramNames)\")\n        //4、生成toObservableXxx方法\n        FunSpec.builder(funName)\n            .addTypeVariables(typeVariableNames)\n            .addParameters(funSpec.parameters)\n            .addCode(funBody.build())  //方法里面的表达式\n            .returns(toFunReturnType)\n            .build()\n            .apply { funList.add(this) }\n    }\n    return funList\n}\n\nprivate fun List<ParameterSpec>.typeToClassParameterSpecs(\n    typeVariableNames: List<TypeVariableName>\n): List<ParameterSpec> {\n    val typeCount = typeVariableNames.size\n    val className = Class::class.asClassName()\n    return mapIndexed { index, parameterSpec ->\n        if (index < typeCount) {\n            val classType = className.parameterizedBy(typeVariableNames[index])\n            parameterSpec.toBuilder(type = classType).build()\n        } else parameterSpec\n    }\n}\n\nprivate fun List<ParameterSpec>.toParamNames(\n    originParamsSpecs: List<ParameterSpec>,\n    typeCount: Int\n): String {\n    val isArrayType = typeCount > 0 && originParamsSpecs.first().isArrayType()\n    val paramNames = StringBuilder()\n    if (isArrayType) {\n        paramNames.append(\"arrayOf(\")\n    }\n    forEachIndexed { index, parameterSpec ->\n        if (index > 0) paramNames.append(\", \")\n        if (parameterSpec.isVararg()) paramNames.append(\"*\")\n        paramNames.append(parameterSpec.name)\n        if (isArrayType && index == typeCount - 1) {\n            paramNames.append(\")\")\n        }\n    }\n    return paramNames.toString()\n}\n\nprivate fun List<ParameterSpec>.toParamNames(typeCount: Int): String {\n    val paramNames = StringBuilder()\n    forEachIndexed { index, parameterSpec ->\n        if (index > 0) paramNames.append(\", \")\n        if (parameterSpec.isVararg()) paramNames.append(\"*\")\n        paramNames.append(parameterSpec.name)\n        if (index < typeCount && parameterSpec.type.isClassType()) {\n            paramNames.append(\" as Type\")\n        }\n    }\n    return paramNames.toString()\n}\n\nprivate fun TypeName.isClassType() = toString().startsWith(\"java.lang.Class\")\n\n@Throws(NoSuchElementException::class)\nprivate fun KSClassDeclaration.checkParserValidClass(resolver: Resolver) {\n    val elementQualifiedName = qualifiedName?.asString()\n    if (!isPublic()) {\n        throw NoSuchElementException(\"The class '$elementQualifiedName' must be public\")\n    }\n    if (isAbstract()) {\n        val msg =\n            \"The class '$elementQualifiedName' is abstract. You can't annotate abstract classes with @${Parser::class.java.simpleName}\"\n        throw NoSuchElementException(msg)\n    }\n\n    val className = \"rxhttp.wrapper.parse.Parser\"\n    if (!asStarProjectedType().instanceOf(className, resolver)) {\n        val msg =\n            \"The class '$elementQualifiedName' annotated with @${Parser::class.java.simpleName} must inherit from $className\"\n        throw NoSuchElementException(msg)\n    }\n\n    val typeParameterList = typeParameters\n    val typeCount = typeParameterList.size\n    if (typeCount > 0) {\n        //查找带 java.lang.reflect.Type 参数的构造方法\n        val isValid = getConstructors().any { it.isValid(typeCount) }\n        if (!isValid) {\n            val funBody = StringBuffer(\"public ${simpleName.asString()}(\")\n            for (i in typeParameterList.indices) {\n                funBody.append(K_TYPE.toString())\n                funBody.append(if (i == typeParameterList.lastIndex) \")\" else \",\")\n            }\n            val msg =\n                \"This class '$elementQualifiedName' must declare '$funBody' constructor fun\"\n            throw NoSuchElementException(msg)\n        }\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/RxHttpExtensions.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getConstructors\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.rxhttp.compiler.common.flapTypeParameterSpecTypes\nimport com.rxhttp.compiler.common.getRxHttpExtensionFileSpec\nimport com.rxhttp.compiler.common.getTypeOfString\nimport com.rxhttp.compiler.common.getTypeVariableString\nimport com.rxhttp.compiler.common.isArrayType\nimport com.rxhttp.compiler.common.toParamNames\nimport com.rxhttp.compiler.isDependenceRxJava\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpKClass\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.KModifier\nimport com.squareup.kotlinpoet.MemberName\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.TypeName\nimport com.squareup.kotlinpoet.TypeVariableName\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.originatingKSFiles\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.toTypeParameterResolver\nimport com.squareup.kotlinpoet.ksp.toTypeVariableName\nimport com.squareup.kotlinpoet.ksp.writeTo\n\n/**\n * User: ljx\n * Date: 2020/3/9\n * Time: 17:04\n */\nclass RxHttpExtensions(private val logger: KSPLogger) {\n\n    private val baseRxHttpName = rxhttpKClass.peerClass(\"BaseRxHttp\")\n    private val callFactoryName = ClassName(\"rxhttp.wrapper\", \"CallFactory\")\n    private val awaitName = ClassName(\"rxhttp.wrapper.coroutines\", \"CallAwait\")\n    private val observableCall = rxhttpKClass.peerClass(\"ObservableCall\")\n\n    private val toFlowXxxFunList = ArrayList<FunSpec>()\n    private val toAwaitXxxFunList = ArrayList<FunSpec>()\n    private val toObservableXxxFunList = ArrayList<FunSpec>()\n\n    //根据@Parser注解，生成toObservableXxx()、toAwaitXxx()、toFlowXxx()系列方法\n    @KspExperimental\n    fun generateRxHttpExtendFun(ksClass: KSClassDeclaration, key: String) {\n\n        //遍历获取泛型类型\n        val typeVariableNames = ksClass.typeParameters.map {\n            it.toTypeVariableName().copy(reified = true)\n        }\n        val onParserFunReturnType = ksClass.getParserTypeParam() ?: return\n\n        val typeCount = typeVariableNames.size  //泛型数量\n        val customParser = ksClass.toClassName()\n        //遍历构造方法\n        for (constructor in ksClass.getConstructors()) {\n            if (!constructor.isValid(typeCount)) continue\n            val classTypeParams = ksClass.typeParameters.toTypeParameterResolver()\n            val functionTypeParams =\n                constructor.typeParameters.toTypeParameterResolver(classTypeParams)\n            val originParameterSpecs = constructor.parameters.map {\n                it.toKParameterSpec(functionTypeParams)\n            }\n            val typeParameterSpecs = originParameterSpecs.flapTypeParameterSpecTypes(typeVariableNames)\n            //根据构造方法参数，获取toObservableXxx方法需要的参数\n            val parameterList = typeParameterSpecs.subList(typeCount, typeParameterSpecs.size)\n\n            val modifiers = ArrayList<KModifier>()\n            if (typeVariableNames.isNotEmpty()) {\n                modifiers.add(KModifier.INLINE)\n            }\n\n            val types = typeVariableNames.getTypeVariableString() // <T>, <K, V> 等\n            val typeOfs = typeVariableNames.getTypeOfString()  // javaTypeOf<T>()等\n            val paramNames = parameterList.toParamNames()  //构造方法参数名列表\n            val finalParams = listOf(typeOfs, paramNames)\n                .filter { it.isNotEmpty() }\n                .joinToString()\n\n            if (typeVariableNames.isNotEmpty() && isDependenceRxJava()) {  //对声明了泛型的解析器，生成kotlin编写的toObservableXxx方法\n                val toObservableXxxFunName = \"toObservable$key\"\n                val toObservableXxxFunBody = \"return $toObservableXxxFunName$types($finalParams)\"\n                FunSpec.builder(toObservableXxxFunName)\n                    .addOriginatingKSFile(ksClass.containingFile!!)\n                    .addModifiers(modifiers)\n                    .receiver(baseRxHttpName)\n                    .addParameters(parameterList)\n                    .addStatement(toObservableXxxFunBody) //方法里面的表达式\n                    .addTypeVariables(typeVariableNames)\n                    .returns(observableCall.parameterizedBy(onParserFunReturnType))\n                    .build()\n                    .apply { toObservableXxxFunList.add(this) }\n            }\n\n            val funBody: String\n            val argType: TypeName =\n                if (typeCount == 1 && onParserFunReturnType is TypeVariableName) {\n                    val baseRxHttp = ClassName(rxHttpPackage, \"BaseRxHttp\")\n                    val wrapFun = \"%T.wrap${customParser.simpleName}\"\n                    funBody = \"return %M($wrapFun($finalParams))\"\n                    baseRxHttp\n                } else {\n                    var params = finalParams\n                    if (typeOfs.isNotEmpty() &&\n                        originParameterSpecs.first().isArrayType() &&\n                        onParserFunReturnType !is TypeVariableName\n                    ) {\n                        params = params.replace(typeOfs, \"arrayOf($typeOfs)\")\n                    }\n                    funBody = \"return %M(%T$types($params))\"\n                    customParser\n                }\n            val toAwait = MemberName(\"rxhttp\", \"toAwait\")\n            FunSpec.builder(\"toAwait$key\")\n                .addOriginatingKSFile(ksClass.containingFile!!)\n                .addModifiers(modifiers)\n                .receiver(callFactoryName)\n                .addParameters(parameterList)\n                .addTypeVariables(typeVariableNames)\n                .addCode(funBody, toAwait, argType)\n                .returns(awaitName.parameterizedBy(onParserFunReturnType))\n                .build()\n                .apply { toAwaitXxxFunList.add(this) }\n\n            val callFlow = ClassName(\"rxhttp.wrapper.coroutines\", \"CallFlow\")\n            val toFlow = MemberName(\"rxhttp\", \"toFlow\")\n            FunSpec.builder(\"toFlow$key\")\n                .addOriginatingKSFile(ksClass.containingFile!!)\n                .addModifiers(modifiers)\n                .receiver(callFactoryName)\n                .addParameters(parameterList)\n                .addTypeVariables(typeVariableNames)\n                .addCode(funBody, toFlow, argType)\n                .returns(callFlow.parameterizedBy(onParserFunReturnType))\n                .build()\n                .apply { toFlowXxxFunList.add(this) }\n        }\n    }\n\n\n    fun generateClassFile(codeGenerator: CodeGenerator, defaultKsFile: KSFile?) {\n        val fileSpec =\n            getRxHttpExtensionFileSpec(toObservableXxxFunList, toAwaitXxxFunList, toFlowXxxFunList)\n        val originatingKSFiles = fileSpec.originatingKSFiles().toMutableList()\n        if (originatingKSFiles.isEmpty()) {\n            defaultKsFile?.let { originatingKSFiles.add(it) }\n        }\n        fileSpec.writeTo(codeGenerator, true, originatingKSFiles)\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/RxHttpGenerator.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.rxhttp.compiler.RxHttp\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpKClass\nimport com.squareup.kotlinpoet.ANY\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.BOOLEAN\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.KModifier\nimport com.squareup.kotlinpoet.LIST\nimport com.squareup.kotlinpoet.LONG\nimport com.squareup.kotlinpoet.MAP\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.PropertySpec\nimport com.squareup.kotlinpoet.STRING\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.TypeVariableName\nimport com.squareup.kotlinpoet.WildcardTypeName\nimport com.squareup.kotlinpoet.asClassName\nimport com.squareup.kotlinpoet.jvm.jvmOverloads\nimport com.squareup.kotlinpoet.jvm.jvmStatic\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport java.io.IOException\n\nclass RxHttpGenerator(\n    private val logger: KSPLogger,\n    private val defaultKsFile: KSFile?\n) {\n\n    var paramsVisitor: ParamsVisitor? = null\n    var domainVisitor: DomainVisitor? = null\n    var converterVisitor: ConverterVisitor? = null\n    var okClientVisitor: OkClientVisitor? = null\n    var defaultDomainVisitor: DefaultDomainVisitor? = null\n\n    //生成RxHttp类\n    @KspExperimental\n    @Throws(IOException::class)\n    fun generateCode(codeGenerator: CodeGenerator) {\n\n        val paramClassName = ClassName(\"rxhttp.wrapper.param\", \"Param\")\n        val typeVariableP = TypeVariableName(\"P\", paramClassName.parameterizedBy(\"P\"))      //泛型P\n        val typeVariableR = TypeVariableName(\"R\", rxhttpKClass.parameterizedBy(\"P\", \"R\")) //泛型R\n\n        val okHttpClient = ClassName(\"okhttp3\", \"OkHttpClient\")\n        val requestName = okHttpClient.peerClass(\"Request\")\n        val headerName = okHttpClient.peerClass(\"Headers\")\n        val headerBuilderName = okHttpClient.peerClass(\"Headers.Builder\")\n        val cacheControlName = okHttpClient.peerClass(\"CacheControl\")\n        val callName = okHttpClient.peerClass(\"Call\")\n\n        val timeUnitName = ClassName(\"java.util.concurrent\", \"TimeUnit\")\n\n        val rxHttpPluginsName = ClassName(\"rxhttp\", \"RxHttpPlugins\")\n        val converterName = ClassName(\"rxhttp.wrapper.callback\", \"IConverter\")\n        val logUtilName = ClassName(\"rxhttp.wrapper.utils\", \"LogUtil\")\n        val logInterceptor = ClassName(\"rxhttp.wrapper.intercept\", \"LogInterceptor\")\n        val cacheInterceptorName = logInterceptor.peerClass(\"CacheInterceptor\")\n        val rangeInterceptor = logInterceptor.peerClass(\"RangeInterceptor\")\n        val cacheModeName = ClassName(\"rxhttp.wrapper.cache\", \"CacheMode\")\n        val cacheStrategyName = cacheModeName.peerClass(\"CacheStrategy\")\n        val downloadOffSizeName = ClassName(\"rxhttp.wrapper.entity\", \"DownloadOffSize\")\n        val outputStreamFactory = converterName.peerClass(\"OutputStreamFactory\")\n\n        val t = TypeVariableName(\"T\")\n        val className = Class::class.asClassName()\n        val superT = WildcardTypeName.consumerOf(t)\n        val classSuperTName = className.parameterizedBy(superT)\n\n        val wildcard = TypeVariableName(\"*\")\n        val listName = LIST.parameterizedBy(\"*\")\n        val mapName = MAP.parameterizedBy(STRING, wildcard)\n        val mapStringName = MAP.parameterizedBy(STRING, STRING)\n\n        val methodList = ArrayList<FunSpec>() //方法集合\n\n        //添加构造方法\n        val constructorFun = FunSpec.constructorBuilder()\n            .addModifiers(KModifier.PROTECTED)\n            .addParameter(\"param\", typeVariableP)\n            .build()\n\n        val propertySpecs = mutableListOf<PropertySpec>()\n\n        PropertySpec.builder(\"connectTimeoutMillis\", LONG, KModifier.PRIVATE)\n            .initializer(\"0L\")\n            .mutable(true)\n            .build()\n            .let { propertySpecs.add(it) }\n\n        PropertySpec.builder(\"readTimeoutMillis\", LONG, KModifier.PRIVATE)\n            .initializer(\"0L\")\n            .mutable(true)\n            .build()\n            .let { propertySpecs.add(it) }\n\n        PropertySpec.builder(\"writeTimeoutMillis\", LONG, KModifier.PRIVATE)\n            .initializer(\"0L\")\n            .mutable(true)\n            .build()\n            .let { propertySpecs.add(it) }\n\n        PropertySpec.builder(\"converter\", converterName, KModifier.PRIVATE)\n            .mutable(true)\n            .initializer(\"%T.getConverter()\", rxHttpPluginsName)\n            .build()\n            .let { propertySpecs.add(it) }\n\n        PropertySpec.builder(\"okClient\", okHttpClient, KModifier.PRIVATE)\n            .mutable(true)\n            .initializer(\"%T.getOkHttpClient()\", rxHttpPluginsName)\n            .build()\n            .let { propertySpecs.add(it) }\n\n        PropertySpec.builder(\"param\", typeVariableP)\n            .initializer(\"param\")\n            .build()\n            .let { propertySpecs.add(it) }\n\n        PropertySpec.builder(\"request\", requestName.copy(true))\n            .mutable(true)\n            .initializer(\"null\")\n            .build()\n            .let { propertySpecs.add(it) }\n\n        val getUrlFun = FunSpec.getterBuilder()\n            .addStatement(\"addDefaultDomainIfAbsent()\")\n            .addStatement(\"return param.url\")\n            .build()\n\n        PropertySpec.builder(\"url\", STRING)\n            .addAnnotation(getJvmName(\"getUrl\"))\n            .getter(getUrlFun)\n            .build()\n            .let { propertySpecs.add(it) }\n\n        val simpleUrlFun = FunSpec.getterBuilder()\n            .addStatement(\"return param.simpleUrl\")\n            .build()\n\n        PropertySpec.builder(\"simpleUrl\", STRING)\n            .addAnnotation(getJvmName(\"getSimpleUrl\"))\n            .getter(simpleUrlFun)\n            .build()\n            .let { propertySpecs.add(it) }\n\n        val headersFun = FunSpec.getterBuilder()\n            .addStatement(\"return param.headers\")\n            .build()\n\n        PropertySpec.builder(\"headers\", headerName)\n            .addAnnotation(getJvmName(\"getHeaders\"))\n            .getter(headersFun)\n            .build()\n            .let { propertySpecs.add(it) }\n\n        val headersBuilderFun = FunSpec.getterBuilder()\n            .addStatement(\"return param.headersBuilder\")\n            .build()\n\n        PropertySpec.builder(\"headersBuilder\", headerBuilderName)\n            .addAnnotation(getJvmName(\"getHeadersBuilder\"))\n            .getter(headersBuilderFun)\n            .build()\n            .let { propertySpecs.add(it) }\n\n        val cacheStrategyFun = FunSpec.getterBuilder()\n            .addStatement(\"return param.cacheStrategy\")\n            .build()\n\n        PropertySpec.builder(\"cacheStrategy\", cacheStrategyName)\n            .addAnnotation(getJvmName(\"getCacheStrategy\"))\n            .getter(cacheStrategyFun)\n            .build()\n            .let { propertySpecs.add(it) }\n\n        val okClientFun = FunSpec.getterBuilder()\n            .addCode(\n                \"\"\"\n                if (_okHttpClient != null) return _okHttpClient!!\n                val okClient = this.okClient\n                var builder: OkHttpClient.Builder? = null\n                \n                if (%T.isDebug()) {\n                    val b = builder ?: okClient.newBuilder().also { builder = it }\n                    b.addInterceptor(%T(okClient))\n                }\n                \n                if (connectTimeoutMillis != 0L) {\n                    val b = builder ?: okClient.newBuilder().also { builder = it }\n                    b.connectTimeout(connectTimeoutMillis, %T.MILLISECONDS)\n                }\n                \n                if (readTimeoutMillis != 0L) {\n                    val b = builder ?: okClient.newBuilder().also { builder = it }\n                    b.readTimeout(readTimeoutMillis, %T.MILLISECONDS)\n                }\n\n                if (writeTimeoutMillis != 0L) {\n                    val b = builder ?: okClient.newBuilder().also { builder = it }\n                    b.writeTimeout(writeTimeoutMillis, %T.MILLISECONDS)\n                }\n                \n                if (param.cacheMode != CacheMode.ONLY_NETWORK) {\n                    val b = builder ?: okClient.newBuilder().also { builder = it }\n                    b.addInterceptor(%T(cacheStrategy))\n                }\n\n                _okHttpClient = builder?.build() ?: okClient\n                return _okHttpClient!!\n                \"\"\".trimIndent(),\n                logUtilName,\n                logInterceptor,\n                timeUnitName,\n                timeUnitName,\n                timeUnitName,\n                cacheInterceptorName\n            )\n            .build()\n\n\n        PropertySpec.builder(\"_okHttpClient\", okHttpClient.copy(true), KModifier.PRIVATE)\n            .mutable(true)\n            .initializer(\"null\")\n            .build()\n            .let { propertySpecs.add(it) }\n\n        PropertySpec.builder(\"okHttpClient\", okHttpClient)\n            .addAnnotation(getJvmName(\"getOkHttpClient\"))\n            .getter(okClientFun)\n            .build()\n            .let { propertySpecs.add(it) }\n\n        FunSpec.builder(\"connectTimeout\")\n            .addParameter(\"connectTimeout\", LONG)\n            .addStatement(\"connectTimeoutMillis = connectTimeout\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"readTimeout\")\n            .addParameter(\"readTimeout\", LONG)\n            .addStatement(\"readTimeoutMillis = readTimeout\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"writeTimeout\")\n            .addParameter(\"writeTimeout\", LONG)\n            .addStatement(\"writeTimeoutMillis = writeTimeout\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        val methodMap = LinkedHashMap<String, String>()\n        methodMap[\"get\"] = \"RxHttpNoBodyParam\"\n        methodMap[\"head\"] = \"RxHttpNoBodyParam\"\n        methodMap[\"postBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"putBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"patchBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"deleteBody\"] = \"RxHttpBodyParam\"\n        methodMap[\"postForm\"] = \"RxHttpFormParam\"\n        methodMap[\"putForm\"] = \"RxHttpFormParam\"\n        methodMap[\"patchForm\"] = \"RxHttpFormParam\"\n        methodMap[\"deleteForm\"] = \"RxHttpFormParam\"\n        methodMap[\"postJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"putJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"patchJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"deleteJson\"] = \"RxHttpJsonParam\"\n        methodMap[\"postJsonArray\"] = \"RxHttpJsonArrayParam\"\n        methodMap[\"putJsonArray\"] = \"RxHttpJsonArrayParam\"\n        methodMap[\"patchJsonArray\"] = \"RxHttpJsonArrayParam\"\n        methodMap[\"deleteJsonArray\"] = \"RxHttpJsonArrayParam\"\n\n        val codeBlock =\n            \"\"\"\n                For example:\n\n                ```\n                RxHttp.get(\"/service/%L/...\", 1)\n                    .addQuery(\"size\", 20)\n                    ...\n                ```\n                 url = /service/1/...?size=20\n            \"\"\".trimIndent()\n\n        val companionBuilder = TypeSpec.companionObjectBuilder()\n\n        methodMap.forEach { (key, value) ->\n            val methodBuilder = FunSpec.builder(key)\n            if (key == \"get\") {\n                methodBuilder.addKdoc(codeBlock, \"%d\")\n            }\n            methodBuilder.jvmStatic()\n                .addParameter(\"url\", STRING)\n                .addParameter(\"formatArgs\", ANY, true, KModifier.VARARG)\n                .addStatement(\n                    \"return $value(%T.${key}(format(url, *formatArgs)))\", paramClassName,\n                )\n                .returns(rxhttpKClass.peerClass(value))\n                .build()\n                .let { companionBuilder.addFunction(it) }\n        }\n\n        paramsVisitor?.apply {\n            companionBuilder.addFunctions(getFunList(codeGenerator))\n        }\n\n        FunSpec.builder(\"format\")\n            .addKdoc(\"Returns a formatted string using the specified format string and arguments.\")\n            .addModifiers(KModifier.PRIVATE)\n            .addParameter(\"url\", STRING)\n            .addParameter(\"formatArgs\", ANY, true, KModifier.VARARG)\n            .addStatement(\"return if(formatArgs.isEmpty()) url else String.format(url, *formatArgs)\")\n            .returns(STRING)\n            .build()\n            .let { companionBuilder.addFunction(it) }\n\n        FunSpec.builder(\"setUrl\")\n            .addParameter(\"url\", STRING)\n            .addStatement(\"param.url = url\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addPath\")\n            .addKdoc(\n                \"\"\"\n                For example:\n\n                ```\n                RxHttp.get(\"/service/{page}/...\")\n                    .addPath(\"page\", 1)\n                    ...\n                ```\n                url = /service/1/...\n                \"\"\".trimIndent()\n            )\n            .addParameter(\"name\", STRING)\n            .addParameter(\"value\", ANY)\n            .addStatement(\"param.addPath(name, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addEncodedPath\")\n            .addParameter(\"name\", STRING)\n            .addParameter(\"value\", ANY)\n            .addStatement(\"param.addEncodedPath(name, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        val isAddParam = ParameterSpec.builder(\"add\", BOOLEAN)\n            .defaultValue(\"true\")\n            .build()\n\n        FunSpec.builder(\"setQuery\")\n            .jvmOverloads()\n            .addParameter(\"key\", STRING)\n            .addParameter(\"value\", ANY, true)\n            .addParameter(isAddParam)\n            .addStatement(\"if (add) param.setQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setEncodedQuery\")\n            .jvmOverloads()\n            .addParameter(\"key\", STRING)\n            .addParameter(\"value\", ANY, true)\n            .addParameter(isAddParam)\n            .addStatement(\"if (add) param.setEncodedQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"removeAllQuery\")\n            .addParameter(\"key\", STRING)\n            .addStatement(\"param.removeAllQuery(key)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addQuery\")\n            .addParameter(\"key\", STRING)\n            .addStatement(\"param.addQuery(key, null)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addEncodedQuery\")\n            .addParameter(\"key\", STRING)\n            .addStatement(\"param.addEncodedQuery(key, null)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addQuery\")\n            .jvmOverloads()\n            .addParameter(\"key\", STRING)\n            .addParameter(\"value\", ANY, true)\n            .addParameter(isAddParam)\n            .addStatement(\"if (add) param.addQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addEncodedQuery\")\n            .jvmOverloads()\n            .addParameter(\"key\", STRING)\n            .addParameter(\"value\", ANY, true)\n            .addParameter(isAddParam)\n            .addStatement(\"if (add) param.addEncodedQuery(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addAllQuery\")\n            .addParameter(\"key\", STRING)\n            .addParameter(\"list\", listName)\n            .addStatement(\"param.addAllQuery(key, list)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addAllEncodedQuery\")\n            .addParameter(\"key\", STRING)\n            .addParameter(\"list\", listName)\n            .addStatement(\"param.addAllEncodedQuery(key, list)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addAllQuery\")\n            .addParameter(\"map\", mapName)\n            .addStatement(\"param.addAllQuery(map)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addAllEncodedQuery\")\n            .addParameter(\"map\", mapName)\n            .addStatement(\"param.addAllEncodedQuery(map)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addHeader\")\n            .jvmOverloads()\n            .addParameter(\"line\", STRING)\n            .addParameter(isAddParam)\n            .addStatement(\"if (add) param.addHeader(line)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addNonAsciiHeader\")\n            .addKdoc(\"Add a header with the specified name and value. Does validation of header names, allowing non-ASCII values.\")\n            .addParameter(\"key\", STRING)\n            .addParameter(\"value\", STRING)\n            .addStatement(\"param.addNonAsciiHeader(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setNonAsciiHeader\")\n            .addKdoc(\"Set a header with the specified name and value. Does validation of header names, allowing non-ASCII values.\")\n            .addParameter(\"key\", STRING)\n            .addParameter(\"value\", STRING)\n            .addStatement(\"param.setNonAsciiHeader(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addHeader\")\n            .jvmOverloads()\n            .addParameter(\"key\", STRING)\n            .addParameter(\"value\", STRING)\n            .addParameter(isAddParam)\n            .addStatement(\"if (add) param.addHeader(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addAllHeader\")\n            .addParameter(\"headers\", mapStringName)\n            .addStatement(\"param.addAllHeader(headers)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"addAllHeader\")\n            .addParameter(\"headers\", headerName)\n            .addStatement(\"param.addAllHeader(headers)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setHeader\")\n            .addParameter(\"key\", STRING)\n            .addParameter(\"value\", STRING)\n            .addStatement(\"param.setHeader(key, value)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setAllHeader\")\n            .addParameter(\"headers\", mapStringName)\n            .addStatement(\"param.setAllHeader(headers)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        val endIndex = ParameterSpec.builder(\"endIndex\", LONG)\n            .defaultValue(\"-1L\")\n            .build()\n\n        FunSpec.builder(\"setRangeHeader\")\n            .jvmOverloads()\n            .addParameter(\"startIndex\", LONG)\n            .addParameter(endIndex)\n            .addStatement(\"return setRangeHeader(startIndex, endIndex, false)\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setRangeHeader\")\n            .addParameter(\"startIndex\", LONG)\n            .addParameter(\"connectLastProgress\", BOOLEAN)\n            .addStatement(\"return setRangeHeader(startIndex, -1, connectLastProgress)\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setRangeHeader\")\n            .addKdoc(\n                \"\"\"\n                设置断点下载开始/结束位置\n                @param startIndex 断点下载开始位置\n                @param endIndex 断点下载结束位置，默认为-1，即默认结束位置为文件末尾\n                @param connectLastProgress 是否衔接上次的下载进度，该参数仅在带进度断点下载时生效\n                \"\"\".trimIndent()\n            )\n            .addParameter(\"startIndex\", LONG)\n            .addParameter(\"endIndex\", LONG)\n            .addParameter(\"connectLastProgress\", BOOLEAN)\n            .addCode(\n                \"\"\"\n                param.setRangeHeader(startIndex, endIndex)\n                if (connectLastProgress && startIndex >= 0)\n                    param.tag(DownloadOffSize::class.java, %T(startIndex))\n                return self()\n                \"\"\".trimIndent(), downloadOffSizeName\n            )\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"removeAllHeader\")\n            .addParameter(\"key\", STRING)\n            .addStatement(\"param.removeAllHeader(key)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setHeadersBuilder\")\n            .addParameter(\"builder\", headerBuilderName)\n            .addStatement(\"param.headersBuilder = builder\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setAssemblyEnabled\")\n            .addKdoc(\n                \"\"\"\n                设置单个接口是否需要添加公共参数,\n                即是否回调[RxHttpPlugins.setOnParamAssembly]方法设置的接口, 默认为true\n                \"\"\".trimIndent()\n            )\n            .addParameter(\"enabled\", BOOLEAN)\n            .addStatement(\"param.isAssemblyEnabled = enabled\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setDecoderEnabled\")\n            .addKdoc(\n                \"\"\"\n                设置单个接口是否需要对Http返回的数据进行解码/解密,\n                即是否回调[RxHttpPlugins.setResultDecoder]方法设置的接口, 默认为true\n                \"\"\".trimIndent()\n            )\n            .addParameter(\"enabled\", BOOLEAN)\n            .addStatement(\n                \"param.addHeader(%T.DATA_DECRYPT, enabled.toString())\", paramClassName\n            )\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"isAssemblyEnabled\")\n            .addStatement(\"return param.isAssemblyEnabled\")\n            .returns(BOOLEAN)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"getHeader\")\n            .addParameter(\"key\", STRING)\n            .addStatement(\"return param.getHeader(key)\")\n            .returns(STRING)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"tag\")\n            .addParameter(\"tag\", ANY)\n            .addStatement(\"param.tag(tag)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"tag\")\n            .addModifiers(KModifier.OVERRIDE, KModifier.PUBLIC)\n            .addTypeVariable(t)\n            .addParameter(\"type\", classSuperTName)\n            .addParameter(\"tag\", t)\n            .addCode(\n                \"\"\"\n            param.tag(type, tag)\n            if (type === %T::class.java) {\n                okClient = okClient.newBuilder()\n                    .addInterceptor(%T())\n                    .build()\n            }\n            return self()\n            \"\"\".trimIndent(), outputStreamFactory, rangeInterceptor\n            )\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"cacheControl\")\n            .addParameter(\"cacheControl\", cacheControlName)\n            .addStatement(\"param.cacheControl(cacheControl)\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setCacheKey\")\n            .addParameter(\"cacheKey\", STRING)\n            .addStatement(\"param.cacheKey = cacheKey\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setCacheValidTime\")\n            .addParameter(\"cacheValidTime\", LONG)\n            .addStatement(\"param.cacheValidTime = cacheValidTime\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setCacheMode\")\n            .addParameter(\"cacheMode\", cacheModeName)\n            .addStatement(\"param.cacheMode = cacheMode\")\n            .addStatement(\"return self()\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"newCall\")\n            .addModifiers(KModifier.OVERRIDE, KModifier.PUBLIC)\n            .addCode(\n                \"\"\"\n                val request = buildRequest()\n                return okHttpClient.newCall(request)\n                \"\"\".trimIndent()\n            )\n            .returns(callName)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"buildRequest\")\n            .addCode(\n                \"\"\"\n                if (request == null) {\n                    doOnStart()\n                    request = param.buildRequest()\n                }\n                return request!!\n                \"\"\".trimIndent()\n            )\n            .returns(requestName)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"doOnStart\")\n            .addModifiers(KModifier.PRIVATE)\n            .addKdoc(\"请求开始前内部调用，用于添加默认域名等操作\\n\")\n            .addStatement(\"setConverterToParam(converter)\")\n            .addStatement(\"addDefaultDomainIfAbsent()\")\n            .build()\n            .let { methodList.add(it) }\n\n        converterVisitor?.apply {\n            methodList.addAll(getFunList())\n        }\n\n        FunSpec.builder(\"setConverter\")\n            .addParameter(\"converter\", converterName)\n            .addCode(\n                \"\"\"\n                this.converter = converter\n                return self()\n                \"\"\".trimIndent()\n            )\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setConverterToParam\")\n            .addKdoc(\"给Param设置转换器，此方法会在请求发起前，被RxHttp内部调用\\n\")\n            .addModifiers(KModifier.PRIVATE)\n            .addParameter(\"converter\", converterName)\n            .addStatement(\"param.tag(IConverter::class.java, converter)\")\n            .build()\n            .let { methodList.add(it) }\n\n        FunSpec.builder(\"setOkClient\")\n            .addParameter(\"okClient\", okHttpClient)\n            .addCode(\n                \"\"\"\n                this.okClient = okClient\n                return self()\n                \"\"\".trimIndent()\n            )\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        okClientVisitor?.apply {\n            methodList.addAll(getFunList())\n        }\n\n        defaultDomainVisitor?.apply {\n            methodList.add(getFun())\n        }\n\n        domainVisitor?.apply {\n            methodList.addAll(getFunList())\n        }\n\n        FunSpec.builder(\"setDomainIfAbsent\")\n            .addParameter(\"domain\", STRING)\n            .addCode(\n                \"\"\"\n                val newUrl = addDomainIfAbsent(param.simpleUrl, domain)\n                param.url = newUrl\n                return self()\n                \"\"\".trimIndent()\n            )\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        //对url添加域名方法\n        FunSpec.builder(\"addDomainIfAbsent\")\n            .addModifiers(KModifier.PRIVATE)\n            .addParameter(\"url\", STRING)\n            .addParameter(\"domain\", STRING)\n            .addCode(\n                $$\"\"\"\n                return \n                    if (url.startsWith(\"http\")) {\n                        url\n                    } else if (url.startsWith(\"/\")) {\n                        val finalUrl = if (domain.endsWith(\"/\")) url.substring(1) else url\n                        \"$domain$finalUrl\"\n                    } else if (domain.endsWith(\"/\")) {\n                        \"$domain$url\"\n                    } else {\n                        \"$domain/$url\"\n                    }\n                \"\"\".trimIndent()\n            )\n            .returns(STRING)\n            .build()\n            .let { methodList.add(it) }\n\n        val suppressAnnotation = AnnotationSpec.builder(Suppress::class)\n            .addMember(\"%S\", \"UNCHECKED_CAST\")\n            .build()\n\n        FunSpec.builder(\"self\")\n            .addModifiers(KModifier.PRIVATE)\n            .addAnnotation(suppressAnnotation)\n            .addStatement(\"return this as R\")\n            .returns(typeVariableR)\n            .build()\n            .let { methodList.add(it) }\n\n        val baseRxHttpName = rxhttpKClass.peerClass(\"BaseRxHttp\")\n\n        val rxHttpBuilder = TypeSpec.classBuilder(RxHttp)\n            .primaryConstructor(constructorFun)\n            .addType(companionBuilder.build())\n            .addKdoc(\n                \"\"\"\n                Github\n                https://github.com/liujingxing/rxhttp\n                https://github.com/liujingxing/rxlife\n                https://github.com/liujingxing/rxhttp/wiki/FAQ\n                https://github.com/liujingxing/rxhttp/wiki/更新日志\n            \"\"\".trimIndent()\n            )\n            .addModifiers(KModifier.OPEN)\n            .addTypeVariable(typeVariableP)\n            .addTypeVariable(typeVariableR)\n            .superclass(baseRxHttpName)\n            .addProperties(propertySpecs)\n            .addFunctions(methodList)\n            .build()\n\n        val kSFiles = mutableListOf<KSFile>()\n        paramsVisitor?.originatingKSFiles?.let { kSFiles.addAll(it) }\n        domainVisitor?.originatingKSFiles?.let { kSFiles.addAll(it) }\n        converterVisitor?.originatingKSFiles?.let { kSFiles.addAll(it) }\n        okClientVisitor?.originatingKSFiles?.let { kSFiles.addAll(it) }\n        defaultDomainVisitor?.originatingKSFile?.let { kSFiles.add(it) }\n\n        if (kSFiles.isEmpty()) {\n            defaultKsFile?.let { kSFiles.add(it) }\n        }\n        FileSpec.builder(rxHttpPackage, RxHttp)\n            .addType(rxHttpBuilder)\n            .indent(\"    \")\n            .build()\n            .writeTo(codeGenerator, true, kSFiles)\n\n        /*\n        Aggregating vs Isolating\n        使用原则:\n        1、如果输出的文件与注解无关 或者 则使用Isolating隔离模式\n        2、如果一个文件，有注解时需要输出，没有注解时，不输出，则使用Isolating隔离模式\n        3、如果输出的文件与依赖于注解，有、无注解时，生成的内容不一样，则使用Aggregating聚合模式\n\n        区别:\n        1、Isolating隔离模式下，当一个源文件改动时，处理器只会处理这个文件\n        2、Aggregating聚合模式下，任意一个源文件改动时，解析器不仅会处理这个文件，还会再次处理注解所在的源文件(如果没使用注解，则不会处理)\n        3、不管是Aggregating还是Isolating，在输出文件时，都可以配置关联的源文件(originatingKSFiles)\n\n        originatingKSFiles参数介绍：\n        1、该参数仅在增量编译时起作用\n        2、全量编译时，输出A、B、C三个文件，其中C配置了关联的源文件D，\n        增量编译时，如果只输出了A、B两个文件，此时ksp会在备份文件中复制C文件到输出目录下(前提时源文件D没有更改)\n\n        https://github.com/liujingxing/rxhttp/issues/489\n        对于不使用任何注解的用户，RxHttp会在所有源文件中，取第一个作为Aggregating聚合模式的关联文件(originatingKSFiles)\n         */\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/java/com/rxhttp/compiler/ksp/RxHttpWrapper.kt",
    "content": "package com.rxhttp.compiler.ksp\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAnnotationsByType\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.google.devtools.ksp.symbol.KSPropertyDeclaration\nimport com.rxhttp.compiler.common.toParamNames\nimport com.rxhttp.compiler.rxHttpPackage\nimport com.rxhttp.compiler.rxhttpKClass\nimport com.squareup.kotlinpoet.ANY\nimport com.squareup.kotlinpoet.CodeBlock\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.KModifier\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.STRING\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.TypeVariableName\nimport com.squareup.kotlinpoet.jvm.jvmStatic\nimport com.squareup.kotlinpoet.ksp.toTypeParameterResolver\nimport com.squareup.kotlinpoet.ksp.toTypeVariableName\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport rxhttp.wrapper.annotation.Converter\nimport rxhttp.wrapper.annotation.Domain\nimport rxhttp.wrapper.annotation.OkClient\nimport rxhttp.wrapper.annotation.Param\n\n/**\n * User: ljx\n * Date: 2020/5/30\n * Time: 19:03\n */\nclass RxHttpWrapper(private val logger: KSPLogger) {\n\n    private val classMap = LinkedHashMap<String, Wrapper>()\n\n    private val elementMap = LinkedHashMap<String, KSClassDeclaration>()\n    private val ksFiles = mutableSetOf<KSFile>()\n\n    @KspExperimental\n    fun add(ksClassDeclaration: KSClassDeclaration) {\n        val annotation =\n            ksClassDeclaration.getAnnotationsByType(Param::class).firstOrNull() ?: return\n        val name: String = annotation.methodName\n        elementMap[name] = ksClassDeclaration\n        ksFiles.add(ksClassDeclaration.containingFile!!)\n    }\n\n    @KspExperimental\n    fun addOkClient(property: KSPropertyDeclaration) {\n        val okClient = property.getAnnotationsByType(OkClient::class).firstOrNull() ?: return\n        if (okClient.className.isEmpty()) return\n        var wrapper = classMap[okClient.className]\n        if (wrapper == null) {\n            wrapper = Wrapper()\n            classMap[okClient.className] = wrapper\n        }\n        if (wrapper.okClientName != null) {\n            val msg = \"@OkClient annotation className cannot be the same\"\n            logger.error(msg, property)\n        }\n        var name = okClient.name\n        if (name.isBlank()) {\n            name = property.simpleName.asString().firstLetterUpperCase()\n        }\n        wrapper.okClientName = name\n        ksFiles.add(property.containingFile!!)\n    }\n\n    @KspExperimental\n    fun addConverter(property: KSPropertyDeclaration) {\n        val converter = property.getAnnotationsByType(Converter::class).firstOrNull() ?: return\n        if (converter.className.isEmpty()) return\n        var wrapper = classMap[converter.className]\n        if (wrapper == null) {\n            wrapper = Wrapper()\n            classMap[converter.className] = wrapper\n        }\n        if (wrapper.converterName != null) {\n            val msg = \"@Converter annotation className cannot be the same\"\n            logger.error(msg, property)\n        }\n        var name = converter.name\n        if (name.isBlank()) {\n            name = property.simpleName.asString().firstLetterUpperCase()\n        }\n        wrapper.converterName = name\n        ksFiles.add(property.containingFile!!)\n    }\n\n    @KspExperimental\n    fun addDomain(property: KSPropertyDeclaration) {\n        val domain = property.getAnnotationsByType(Domain::class).firstOrNull() ?: return\n        if (domain.className.isEmpty()) return\n        var wrapper = classMap[domain.className]\n        if (wrapper == null) {\n            wrapper = Wrapper()\n            classMap[domain.className] = wrapper\n        }\n        if (wrapper.domainName != null) {\n            val msg = \"@Domain annotation className cannot be the same\"\n            logger.error(msg, property)\n        }\n        var name = domain.name\n        if (name.isBlank()) {\n            name = property.simpleName.asString().firstLetterUpperCase()\n        }\n        wrapper.domainName = name\n        ksFiles.add(property.containingFile!!)\n    }\n\n    @KspExperimental\n    fun generateRxWrapper(codeGenerator: CodeGenerator) {\n        val requestFunList = generateRequestFunList()\n\n        //生成多个RxHttp的包装类\n        classMap.forEach { (className, wrapper) ->\n            val funBody = CodeBlock.builder()\n            wrapper.converterName?.let {\n                funBody.addStatement(\"set$it()\")\n            }\n            wrapper.okClientName?.let {\n                funBody.addStatement(\"set$it()\")\n            }\n            wrapper.domainName?.let {\n                funBody.addStatement(\"setDomainTo${it}IfAbsent()\")\n            }\n            val wildcard = TypeVariableName(\"*\")\n            val rxHttpName = rxhttpKClass.parameterizedBy(wildcard, wildcard)\n            val typeVariable = TypeVariableName(\"R\", rxHttpName)\n            val funList = ArrayList<FunSpec>()\n            FunSpec.builder(\"wrapper\")\n                .addKdoc(\"本类所有方法都会调用本方法\")\n                .addModifiers(KModifier.PRIVATE)\n                .receiver(typeVariable)\n                .addTypeVariable(typeVariable)\n                .addCode(funBody.build())\n                .addCode(\"return this\")\n                .returns(typeVariable)\n                .build()\n                .apply { funList.add(this) }\n            funList.addAll(requestFunList)\n\n            val rxHttpBuilder = TypeSpec.objectBuilder(\"Rx${className}Http\")\n                .addKdoc(\n                    \"\"\"\n                    本类由@Converter、@Domain、@OkClient注解中的className字段生成  类命名方式: Rx + {className字段值} + Http\n                    Github\n                    https://github.com/liujingxing/rxhttp\n                    https://github.com/liujingxing/rxlife\n                    https://github.com/liujingxing/rxhttp/wiki/FAQ\n                    https://github.com/liujingxing/rxhttp/wiki/更新日志\n                \"\"\".trimIndent()\n                )\n                .addFunctions(funList)\n\n            FileSpec.builder(rxHttpPackage, \"Rx${className}Http\")\n                .addType(rxHttpBuilder.build())\n                .build()\n                .writeTo(codeGenerator, false, ksFiles)\n        }\n    }\n\n\n    @KspExperimental\n    private fun generateRequestFunList(): ArrayList<FunSpec> {\n        val funList = ArrayList<FunSpec>() //方法集合\n        val funMap = LinkedHashMap<String, String>()\n        funMap[\"get\"] = \"RxHttpNoBodyParam\"\n        funMap[\"head\"] = \"RxHttpNoBodyParam\"\n        funMap[\"postBody\"] = \"RxHttpBodyParam\"\n        funMap[\"putBody\"] = \"RxHttpBodyParam\"\n        funMap[\"patchBody\"] = \"RxHttpBodyParam\"\n        funMap[\"deleteBody\"] = \"RxHttpBodyParam\"\n        funMap[\"postForm\"] = \"RxHttpFormParam\"\n        funMap[\"putForm\"] = \"RxHttpFormParam\"\n        funMap[\"patchForm\"] = \"RxHttpFormParam\"\n        funMap[\"deleteForm\"] = \"RxHttpFormParam\"\n        funMap[\"postJson\"] = \"RxHttpJsonParam\"\n        funMap[\"putJson\"] = \"RxHttpJsonParam\"\n        funMap[\"patchJson\"] = \"RxHttpJsonParam\"\n        funMap[\"deleteJson\"] = \"RxHttpJsonParam\"\n        funMap[\"postJsonArray\"] = \"RxHttpJsonArrayParam\"\n        funMap[\"putJsonArray\"] = \"RxHttpJsonArrayParam\"\n        funMap[\"patchJsonArray\"] = \"RxHttpJsonArrayParam\"\n        funMap[\"deleteJsonArray\"] = \"RxHttpJsonArrayParam\"\n        funMap.forEach { (key, value) ->\n            val returnType = rxhttpKClass.peerClass(value)\n            FunSpec.builder(key)\n                .jvmStatic()\n                .addParameter(\"url\", STRING)\n                .addParameter(\"formatArgs\", ANY, true, KModifier.VARARG)\n                .addStatement(\"return RxHttp.${key}(url, *formatArgs).wrapper()\")\n                .returns(returnType)\n                .build()\n                .apply { funList.add(this) }\n        }\n\n        elementMap.forEach { (key, ksClass) ->\n            val rxHttpTypeNames = ksClass.typeParameters.map {\n                it.toTypeVariableName()\n            }\n\n            val rxHttpName = \"RxHttp${ksClass.simpleName.asString()}\"\n            val rxHttpParamName = rxhttpKClass.peerClass(rxHttpName)\n            val funReturnType = if (rxHttpTypeNames.isNotEmpty()) {\n                rxHttpParamName.parameterizedBy(*rxHttpTypeNames.toTypedArray())\n            } else {\n                rxHttpParamName\n            }\n\n            val classTypeParams = ksClass.typeParameters.toTypeParameterResolver()\n            //遍历public构造方法\n            ksClass.getPublicConstructors().forEach { function ->\n                val functionTypeParams = function.typeParameters\n                    .toTypeParameterResolver(classTypeParams)\n                //构造方法参数\n                val parameterSpecs = function.parameters\n                    .mapTo(ArrayList()) { it.toKParameterSpec(functionTypeParams) }\n                if (parameterSpecs.firstOrNull()?.type == STRING) {\n                    parameterSpecs.add(newParameterSpec(\"formatArgs\", ANY, true, KModifier.VARARG))\n                }\n                val prefix = \"return RxHttp.$key(\"\n                val postfix = \").wrapper()\"\n                val funBody = parameterSpecs.toParamNames(prefix, postfix)\n                FunSpec.builder(key)\n                    .jvmStatic()\n                    .addParameters(parameterSpecs)\n                    .addTypeVariables(rxHttpTypeNames)\n                    .addStatement(funBody)\n                    .returns(funReturnType)\n                    .build()\n                    .apply { funList.add(this) }\n            }\n        }\n        return funList\n    }\n\n    class Wrapper {\n        var domainName: String? = null\n        var converterName: String? = null\n        var okClientName: String? = null\n    }\n}"
  },
  {
    "path": "rxhttp-compiler/src/main/resources/META-INF/gradle/incremental.annotation.processors",
    "content": "com.rxhttp.compiler.KaptProcessor,dynamic"
  },
  {
    "path": "rxhttp-compiler/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider",
    "content": "com.rxhttp.compiler.KspProvider"
  },
  {
    "path": "rxhttp-compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor",
    "content": "com.rxhttp.compiler.KaptProcessor"
  },
  {
    "path": "rxhttp-converter/converter-fastjson/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "rxhttp-converter/converter-fastjson/build.gradle",
    "content": "plugins {\n    id 'java-library'\n}\napply from: '../../maven.gradle'\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    compileOnly projects.rxhttp\n    compileOnly libs.okhttp\n    api libs.fastjson\n}\n\njava {\n    sourceCompatibility = \"$jdk_version\"\n    targetCompatibility = \"$jdk_version\"\n}"
  },
  {
    "path": "rxhttp-converter/converter-fastjson/src/main/java/rxhttp/wrapper/converter/FastJsonConverter.java",
    "content": "package rxhttp.wrapper.converter;\n\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.parser.ParserConfig;\nimport com.alibaba.fastjson.serializer.SerializeConfig;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\n\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okhttp3.ResponseBody;\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.wrapper.callback.JsonConverter;\n\n/**\n * User: ljx\n * Date: 2019-11-24\n * Time: 15:34\n */\npublic class FastJsonConverter implements JsonConverter {\n\n    private final SerializeConfig serializeConfig;\n    private final ParserConfig parserConfig;\n    private final MediaType contentType;\n\n    private FastJsonConverter(ParserConfig parserConfig, SerializeConfig serializeConfig, MediaType contentType) {\n        this.parserConfig = parserConfig;\n        this.serializeConfig = serializeConfig;\n        this.contentType = contentType;\n    }\n\n    public static FastJsonConverter create() {\n        return create(ParserConfig.getGlobalInstance(), SerializeConfig.getGlobalInstance());\n    }\n\n    public static FastJsonConverter create(ParserConfig parserConfig) {\n        return create(parserConfig, SerializeConfig.getGlobalInstance());\n    }\n\n    public static FastJsonConverter create(SerializeConfig serializeConfig) {\n        return create(ParserConfig.getGlobalInstance(), serializeConfig);\n    }\n\n    public static FastJsonConverter create(ParserConfig parserConfig, SerializeConfig serializeConfig) {\n        return create(parserConfig, serializeConfig, JsonConverter.MEDIA_TYPE);\n    }\n\n    public static FastJsonConverter create(ParserConfig parserConfig, SerializeConfig serializeConfig, MediaType contentType) {\n        if (parserConfig == null) throw new NullPointerException(\"parserConfig == null\");\n        if (serializeConfig == null) throw new NullPointerException(\"serializeConfig == null\");\n        return new FastJsonConverter(parserConfig, serializeConfig, contentType);\n    }\n\n    @NotNull\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public <T> T convert(@NotNull ResponseBody body, @NotNull Type type, boolean needDecodeResult) throws IOException {\n        try {\n            String result = body.string();\n            if (needDecodeResult) {\n                result = RxHttpPlugins.onResultDecoder(result);\n            }\n            if (type == String.class) {\n                return (T) result;\n            }\n            T t = JSON.parseObject(result, type, parserConfig);\n            if (t == null) {\n                throw new IllegalStateException(\"FastJsonConverter Could not deserialize body as \" + type);\n            }\n            return t;\n        } finally {\n            body.close();\n        }\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public <T> RequestBody convert(T value) throws IOException {\n        return RequestBody.create(contentType, JSON.toJSONBytes(value, serializeConfig));\n    }\n}\n"
  },
  {
    "path": "rxhttp-converter/converter-jackson/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "rxhttp-converter/converter-jackson/build.gradle",
    "content": "plugins {\n    id 'java-library'\n}\napply from: '../../maven.gradle'\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    compileOnly projects.rxhttp\n    compileOnly libs.okhttp\n    api libs.jackson.core\n    api libs.jackson.databind\n    api libs.jackson.annotations\n}\n\njava {\n    sourceCompatibility = \"$jdk_version\"\n    targetCompatibility = \"$jdk_version\"\n}"
  },
  {
    "path": "rxhttp-converter/converter-jackson/src/main/java/rxhttp/wrapper/converter/JacksonConverter.java",
    "content": "package rxhttp.wrapper.converter;\n\nimport com.fasterxml.jackson.databind.JavaType;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.ObjectReader;\nimport com.fasterxml.jackson.databind.ObjectWriter;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\n\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okhttp3.ResponseBody;\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.wrapper.callback.JsonConverter;\n\n/**\n * User: ljx\n * Date: 2019-11-24\n * Time: 15:34\n */\npublic class JacksonConverter implements JsonConverter {\n\n    private final ObjectMapper mapper;\n    private final MediaType contentType;\n\n    private JacksonConverter(ObjectMapper mapper, MediaType contentType) {\n        this.mapper = mapper;\n        this.contentType = contentType;\n    }\n\n    public static JacksonConverter create() {\n        return create(new ObjectMapper());\n    }\n\n    public static JacksonConverter create(ObjectMapper mapper) {\n        return create(mapper, JsonConverter.MEDIA_TYPE);\n    }\n\n    public static JacksonConverter create(ObjectMapper mapper, MediaType contentType) {\n        if (mapper == null) throw new NullPointerException(\"mapper == null\");\n        return new JacksonConverter(mapper, contentType);\n    }\n\n    @NotNull\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public <T> T convert(@NotNull ResponseBody body, @NotNull Type type, boolean needDecodeResult) throws IOException {\n        try {\n            JavaType javaType = mapper.getTypeFactory().constructType(type);\n            ObjectReader reader = mapper.readerFor(javaType);\n            String result = body.string();\n            if (needDecodeResult) {\n                result = RxHttpPlugins.onResultDecoder(result);\n            }\n            if (type == String.class) {\n                return (T) result;\n            }\n            T t = reader.readValue(result);\n            if (t == null) {\n                throw new IllegalStateException(\"JacksonConverter Could not deserialize body as \" + type);\n            }\n            return t;\n        } finally {\n            body.close();\n        }\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public <T> RequestBody convert(T value) throws IOException {\n        JavaType javaType = mapper.getTypeFactory().constructType(value.getClass());\n        ObjectWriter writer = mapper.writerFor(javaType);\n        byte[] bytes = writer.writeValueAsBytes(value);\n        return RequestBody.create(contentType, bytes);\n    }\n}\n"
  },
  {
    "path": "rxhttp-converter/converter-moshi/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "rxhttp-converter/converter-moshi/build.gradle",
    "content": "plugins {\n    id 'java-library'\n}\napply from: '../../maven.gradle'\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    compileOnly projects.rxhttp\n    compileOnly libs.okhttp\n    api libs.moshi\n}\n\njava {\n    sourceCompatibility = \"$jdk_version\"\n    targetCompatibility = \"$jdk_version\"\n}\n\n"
  },
  {
    "path": "rxhttp-converter/converter-moshi/src/main/java/rxhttp/wrapper/converter/MoshiConverter.java",
    "content": "package rxhttp.wrapper.converter;\n\nimport com.squareup.moshi.JsonAdapter;\nimport com.squareup.moshi.JsonDataException;\nimport com.squareup.moshi.JsonReader;\nimport com.squareup.moshi.JsonWriter;\nimport com.squareup.moshi.Moshi;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.util.List;\nimport java.util.Map;\n\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okhttp3.ResponseBody;\nimport okio.Buffer;\nimport okio.BufferedSource;\nimport okio.ByteString;\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.wrapper.callback.JsonConverter;\n\n/**\n * User: ljx\n * Date: 2020/08/06\n * Time: 15:34\n */\npublic class MoshiConverter implements JsonConverter {\n\n    private static final ByteString UTF8_BOM = ByteString.decodeHex(\"EFBBBF\");\n\n    private final Moshi moshi;\n    private final boolean lenient;\n    private final boolean failOnUnknown;\n    private final boolean serializeNulls;\n    private final MediaType contentType;\n\n    private MoshiConverter(Moshi moshi, boolean lenient, boolean failOnUnknown, boolean serializeNulls, MediaType contentType) {\n        this.moshi = moshi;\n        this.lenient = lenient;\n        this.failOnUnknown = failOnUnknown;\n        this.serializeNulls = serializeNulls;\n        this.contentType = contentType;\n    }\n\n    /**\n     * Create an instance using a default {@link Moshi} instance for conversion.\n     *\n     * @return MoshiConverter\n     */\n    public static MoshiConverter create() {\n        return create(new Moshi.Builder().build());\n    }\n\n    /**\n     * Create an instance using {@code moshi} for conversion.\n     *\n     * @param moshi Moshi\n     * @return MoshiConverter\n     */\n    public static MoshiConverter create(Moshi moshi) {\n        return create(moshi, JsonConverter.MEDIA_TYPE);\n    }\n\n    /**\n     * Create an instance using {@code moshi} for conversion.\n     *\n     * @param moshi Moshi\n     * @return MoshiConverter\n     */\n    public static MoshiConverter create(Moshi moshi, MediaType contentType) {\n        if (moshi == null) throw new NullPointerException(\"moshi == null\");\n        return new MoshiConverter(moshi, false, false, false, contentType);\n    }\n\n    /**\n     * Return a new factory which uses {@linkplain JsonAdapter#lenient() lenient} adapters.\n     *\n     * @return MoshiConverter\n     */\n    public MoshiConverter asLenient() {\n        return new MoshiConverter(moshi, true, failOnUnknown, serializeNulls, contentType);\n    }\n\n    /**\n     * Return a new factory which uses {@link JsonAdapter#failOnUnknown()} adapters.\n     *\n     * @return MoshiConverter\n     */\n    public MoshiConverter failOnUnknown() {\n        return new MoshiConverter(moshi, lenient, true, serializeNulls, contentType);\n    }\n\n    /**\n     * Return a new factory which includes null values into the serialized JSON.\n     *\n     * @return MoshiConverter\n     */\n    public MoshiConverter withNullSerialization() {\n        return new MoshiConverter(moshi, lenient, failOnUnknown, true, contentType);\n    }\n\n    @NotNull\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public <T> T convert(@NotNull ResponseBody body, @NotNull Type type, boolean needDecodeResult) throws IOException {\n        JsonAdapter<T> adapter = moshi.adapter(type);\n        if (lenient) {\n            adapter = adapter.lenient();\n        }\n        if (failOnUnknown) {\n            adapter = adapter.failOnUnknown();\n        }\n        if (serializeNulls) {\n            adapter = adapter.serializeNulls();\n        }\n        try {\n            BufferedSource source;\n            if (needDecodeResult || type == String.class) {\n                String result = body.string();\n                if (needDecodeResult) {\n                    result = RxHttpPlugins.onResultDecoder(result);\n                }\n                if (type == String.class) {\n                    return (T) result;\n                }\n                source = new Buffer().writeUtf8(result);\n            } else {\n                source = body.source();\n            }\n            // Moshi has no document-level API so the responsibility of BOM skipping falls to whatever\n            // is delegating to it. Since it's a UTF-8-only library as well we only honor the UTF-8 BOM.\n            if (source.rangeEquals(0, UTF8_BOM)) {\n                source.skip(UTF8_BOM.size());\n            }\n            JsonReader reader = JsonReader.of(source);\n            T result = adapter.fromJson(reader);\n            if (reader.peek() != JsonReader.Token.END_DOCUMENT) {\n                throw new JsonDataException(\"JSON document was not fully consumed.\");\n            }\n            if (result == null) {\n                throw new IllegalStateException(\"MoshiConverter Could not deserialize body as \" + type);\n            }\n            return result;\n        } finally {\n            body.close();\n        }\n    }\n\n    @SuppressWarnings({\"unchecked\", \"deprecation\"})\n    @Override\n    public <T> RequestBody convert(T value) throws IOException {\n        Class<T> clazz;\n        if (value instanceof Map) {\n            clazz = (Class<T>) Map.class;\n        } else if (value instanceof List) {\n            clazz = (Class<T>) List.class;\n        } else {\n            clazz = (Class<T>) value.getClass();\n        }\n        JsonAdapter<T> adapter = moshi.adapter(clazz);\n        if (lenient) {\n            adapter = adapter.lenient();\n        }\n        if (failOnUnknown) {\n            adapter = adapter.failOnUnknown();\n        }\n        if (serializeNulls) {\n            adapter = adapter.serializeNulls();\n        }\n        Buffer buffer = new Buffer();\n        JsonWriter writer = JsonWriter.of(buffer);\n        adapter.toJson(writer, value);\n        return RequestBody.create(contentType, buffer.readByteString());\n    }\n}"
  },
  {
    "path": "rxhttp-converter/converter-protobuf/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "rxhttp-converter/converter-protobuf/build.gradle",
    "content": "plugins {\n    id 'java-library'\n}\napply from: '../../maven.gradle'\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    compileOnly projects.rxhttp\n    compileOnly libs.okhttp\n    api libs.protobuf.java\n}\n\njava {\n    sourceCompatibility = \"$jdk_version\"\n    targetCompatibility = \"$jdk_version\"\n}"
  },
  {
    "path": "rxhttp-converter/converter-protobuf/src/main/java/rxhttp/wrapper/converter/ProtoConverter.java",
    "content": "package rxhttp.wrapper.converter;\n\nimport com.google.protobuf.ExtensionRegistryLite;\nimport com.google.protobuf.InvalidProtocolBufferException;\nimport com.google.protobuf.MessageLite;\nimport com.google.protobuf.Parser;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Type;\n\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okhttp3.ResponseBody;\nimport rxhttp.wrapper.callback.IConverter;\n\n/**\n * User: ljx\n * Date: 2019-11-24\n * Time: 14:53\n */\npublic class ProtoConverter implements IConverter {\n\n    private final ExtensionRegistryLite registry;\n    private final MediaType contentType;\n\n    public ProtoConverter() {\n        this(null);\n    }\n\n    public ProtoConverter(ExtensionRegistryLite registry) {\n        this(registry, MediaType.get(\"application/x-protobuf\"));\n    }\n\n    public ProtoConverter(ExtensionRegistryLite registry, MediaType contentType) {\n        this.registry = registry;\n        this.contentType = contentType;\n    }\n\n    @NotNull\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public <T> T convert(@NotNull ResponseBody body, @NotNull Type type, boolean needDecodeResult) throws IOException {\n        try {\n            if (!(type instanceof Class<?>)) {\n                throw new IllegalArgumentException(\"type must be a Class, but it was \" + type);\n            }\n            Class<?> c = (Class<?>) type;\n            if (!MessageLite.class.isAssignableFrom(c)) {\n                throw new IllegalArgumentException(\"type must be MessageLite.class, but it was \" + type);\n            }\n            Parser<MessageLite> parser = getParser(c);\n            T t = (T) (registry == null ? parser.parseFrom(body.byteStream())\n                : parser.parseFrom(body.byteStream(), registry));\n            if (t == null) {\n                throw new IllegalStateException(\"ProtoConverter Could not deserialize body as \" + type);\n            }\n            return t;\n        } catch (InvalidProtocolBufferException e) {\n            throw new RuntimeException(e); // Despite extending IOException, this is data mismatch.\n        } finally {\n            body.close();\n        }\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public <T> RequestBody convert(T value) throws IOException {\n        if (!(value instanceof MessageLite)) return null;\n        MessageLite messageLite = (MessageLite) value;\n        byte[] bytes = messageLite.toByteArray();\n        return RequestBody.create(contentType, bytes);\n    }\n\n    private static Parser<MessageLite> getParser(Class<?> c) {\n        Parser<MessageLite> parser;\n        try {\n            Method method = c.getDeclaredMethod(\"parser\");\n            //noinspection unchecked\n            parser = (Parser<MessageLite>) method.invoke(null);\n        } catch (InvocationTargetException e) {\n            throw new RuntimeException(e.getCause());\n        } catch (NoSuchMethodException | IllegalAccessException ignored) {\n            // If the method is missing, fall back to original static field for pre-3.0 support.\n            try {\n                Field field = c.getDeclaredField(\"PARSER\");\n                //noinspection unchecked\n                parser = (Parser<MessageLite>) field.get(null);\n            } catch (NoSuchFieldException | IllegalAccessException e) {\n                throw new IllegalArgumentException(\"Found a protobuf message but \"\n                    + c.getName()\n                    + \" had no parser() method or PARSER field.\");\n            }\n        }\n        return parser;\n    }\n}\n"
  },
  {
    "path": "rxhttp-converter/converter-serialization/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "rxhttp-converter/converter-serialization/build.gradle",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n    id 'java-library'\n    id 'org.jetbrains.kotlin.jvm'\n}\napply from: '../../maven.gradle'\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    compileOnly projects.rxhttp\n    compileOnly libs.okhttp\n    api libs.kotlinx.serialization.json\n}\n\njava {\n    sourceCompatibility = \"$jdk_version\"\n    targetCompatibility = \"$jdk_version\"\n}\n\nkotlin {\n//    jvmToolchain(jdk_version as int)\n    compilerOptions {\n        jvmTarget = JvmTarget.JVM_1_8\n    }\n}"
  },
  {
    "path": "rxhttp-converter/converter-serialization/src/main/java/rxhttp/wrapper/converter/SerializationConverter.kt",
    "content": "@file:JvmName(\"KotlinSerializationConverter\")\n\npackage rxhttp.wrapper.converter\n\nimport kotlinx.serialization.StringFormat\nimport kotlinx.serialization.serializer\nimport okhttp3.MediaType\nimport okhttp3.RequestBody\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport okhttp3.ResponseBody\nimport rxhttp.RxHttpPlugins\nimport rxhttp.wrapper.callback.JsonConverter\nimport rxhttp.wrapper.utils.JSONStringer\nimport java.lang.reflect.Type\n\nclass SerializationConverter(\n    private val format: StringFormat,\n    private val contentType: MediaType?,\n) : JsonConverter {\n\n    @Suppress(\"UNCHECKED_CAST\")\n    override fun <T : Any> convert(\n        body: ResponseBody,\n        type: Type,\n        needDecodeResult: Boolean\n    ): T {\n        var json = body.string()\n        if (needDecodeResult) {\n            json = RxHttpPlugins.onResultDecoder(json)\n        }\n        if (type == String::class.java) {\n            return json as T\n        }\n        val serializer = format.serializersModule.serializer(type)\n        return format.decodeFromString(serializer, json) as T\n    }\n\n    override fun <T : Any> convert(value: T): RequestBody {\n        val json = when (value) {\n            is Collection<*> -> {\n                JSONStringer().setSerializeCallback {\n                    val serializer = format.serializersModule.serializer(it.javaClass)\n                    format.encodeToString(serializer, it)\n                }.write(value).toString()\n            }\n            is Map<*, *> -> {\n                JSONStringer().setSerializeCallback {\n                    val serializer = format.serializersModule.serializer(it.javaClass)\n                    format.encodeToString(serializer, it)\n                }.write(value).toString()\n            }\n            else -> {\n                val serializer = format.serializersModule.serializer(value::class.java)\n                format.encodeToString(serializer, value)\n            }\n        }\n        return json.toRequestBody(contentType)\n    }\n}\n\n@JvmOverloads\n@JvmName(\"create\")\nfun StringFormat.asConverter(contentType: MediaType? = JsonConverter.MEDIA_TYPE) =\n    SerializationConverter(this, contentType)"
  },
  {
    "path": "rxhttp-converter/converter-simplexml/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "rxhttp-converter/converter-simplexml/build.gradle",
    "content": "plugins {\n    id 'java-library'\n}\napply from: '../../maven.gradle'\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    compileOnly projects.rxhttp\n    compileOnly libs.okhttp\n    api libs.simple.xml\n}\n\njava {\n    sourceCompatibility = \"$jdk_version\"\n    targetCompatibility = \"$jdk_version\"\n}"
  },
  {
    "path": "rxhttp-converter/converter-simplexml/src/main/java/rxhttp/wrapper/converter/XmlConverter.java",
    "content": "package rxhttp.wrapper.converter;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.simpleframework.xml.Serializer;\nimport org.simpleframework.xml.core.Persister;\n\nimport java.io.IOException;\nimport java.io.OutputStreamWriter;\nimport java.lang.reflect.Type;\n\nimport okhttp3.MediaType;\nimport okhttp3.RequestBody;\nimport okhttp3.ResponseBody;\nimport okio.Buffer;\nimport rxhttp.RxHttpPlugins;\nimport rxhttp.wrapper.callback.IConverter;\n\n/**\n * User: ljx\n * Date: 2019-11-21\n * Time: 22:19\n */\npublic class XmlConverter implements IConverter {\n    private static final MediaType MEDIA_TYPE = MediaType.get(\"application/xml; charset=UTF-8\");\n    private static final String CHARSET = \"UTF-8\";\n\n    private final Serializer serializer;\n    private final boolean strict;\n    private final MediaType contentType;\n\n    private XmlConverter(Serializer serializer, boolean strict, MediaType contentType) {\n        this.serializer = serializer;\n        this.strict = strict;\n        this.contentType = contentType;\n    }\n\n    public static XmlConverter create() {\n        return create(new Persister());\n    }\n\n    public static XmlConverter create(Serializer serializer) {\n        return create(serializer, MEDIA_TYPE);\n    }\n\n    public static XmlConverter create(Serializer serializer, MediaType contentType) {\n        return new XmlConverter(serializer, true, contentType);\n    }\n\n    public static XmlConverter createNonStrict() {\n        return createNonStrict(new Persister());\n    }\n\n    public static XmlConverter createNonStrict(Serializer serializer) {\n        return createNonStrict(serializer, MEDIA_TYPE);\n    }\n\n    // Guarding public API nullability.\n    public static XmlConverter createNonStrict(Serializer serializer, MediaType contentType) {\n        if (serializer == null) throw new NullPointerException(\"serializer == null\");\n        return new XmlConverter(serializer, false, contentType);\n    }\n\n    @NotNull\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public <T> T convert(@NotNull ResponseBody body, @NotNull Type type, boolean needDecodeResult) throws IOException {\n        try {\n            if (!(type instanceof Class<?>)) {\n                throw new IllegalArgumentException(\"type must be a Class, but it was \" + type);\n            }\n            Class<T> cls = (Class<T>) type;\n            T read;\n            if (needDecodeResult || type == String.class) {\n                String result = body.string();\n                if (needDecodeResult) {\n                    result = RxHttpPlugins.onResultDecoder(result);\n                }\n                if (type == String.class) {\n                    return (T) result;\n                }\n                read = serializer.read(cls, result, strict);\n            } else {\n                read = serializer.read(cls, body.charStream(), strict);\n            }\n            if (read == null) {\n                throw new IllegalStateException(\"XmlConverter Could not deserialize body as \" + cls);\n            }\n            return read;\n        } catch (RuntimeException | IOException e) {\n            throw e;\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        } finally {\n            body.close();\n        }\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public <T> RequestBody convert(T value) throws IOException {\n        Buffer buffer = new Buffer();\n        try {\n            OutputStreamWriter osw = new OutputStreamWriter(buffer.outputStream(), CHARSET);\n            serializer.write(value, osw);\n            osw.flush();\n        } catch (RuntimeException | IOException e) {\n            throw e;\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n        return RequestBody.create(contentType, buffer.readByteString());\n    }\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        google {\n            content {\n                includeGroupByRegex(\"com\\\\.android.*\")\n                includeGroupByRegex(\"com\\\\.google.*\")\n                includeGroupByRegex(\"androidx.*\")\n            }\n        }\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\n\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n        maven { url 'https://jitpack.io' }\n    }\n}\n\nrootProject.name = \"RxHttp-Project\"\nenableFeaturePreview(\"TYPESAFE_PROJECT_ACCESSORS\")\n\ninclude(':app')\ninclude(':rxhttp')\ninclude(':rxhttp-compiler')\ninclude(':rxhttp-annotation')\ninclude('rxhttp-converter:converter-fastjson')\ninclude('rxhttp-converter:converter-jackson')\ninclude('rxhttp-converter:converter-moshi')\ninclude('rxhttp-converter:converter-protobuf')\ninclude('rxhttp-converter:converter-serialization')\ninclude('rxhttp-converter:converter-simplexml')\n"
  }
]