├── .github ├── beantojson_factory.gif ├── dependabot.yml ├── jetbrains-variant.png └── workflows │ ├── build.yml │ ├── release.yml │ └── run-ui-tests.yml ├── .gitignore ├── .run ├── Run IDE for UI Tests.run.xml ├── Run IDE with Plugin.run.xml ├── Run Plugin Tests.run.xml ├── Run Plugin Verification.run.xml └── Run Qodana.run.xml ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── qodana.yml ├── settings.gradle.kts ├── src ├── main │ ├── kotlin │ │ └── com │ │ │ └── github │ │ │ └── zhangruiyu │ │ │ └── flutterjsonbeanfactory │ │ │ ├── Flag.kt │ │ │ ├── action │ │ │ ├── class_body_generate │ │ │ │ ├── DartGenerateCopyAction.kt │ │ │ │ ├── DartGenerateCopyFix.kt │ │ │ │ └── DartGenerateCopyHandler.kt │ │ │ ├── dart_to_helper │ │ │ │ ├── FlutterBeanFactoryAction.kt │ │ │ │ ├── model │ │ │ │ │ └── FieldInfo.kt │ │ │ │ └── node │ │ │ │ │ ├── ClassGeneratorInfoModel.kt │ │ │ │ │ └── GeneratorDartClassNodeToHelperInfo.kt │ │ │ └── jsontodart │ │ │ │ ├── ClassDefinition.kt │ │ │ │ ├── CollectInfo.kt │ │ │ │ ├── JsonToDartBeanAction.kt │ │ │ │ ├── ModelGenerator.kt │ │ │ │ └── utils │ │ │ │ └── Helper.kt │ │ │ ├── file │ │ │ └── FileHelpers.kt │ │ │ ├── setting │ │ │ ├── SettingComponent.kt │ │ │ ├── SettingLayout.kt │ │ │ └── Settings.kt │ │ │ ├── ui │ │ │ ├── JsonInputDialog.kt │ │ │ └── VerticalFlowLayout.kt │ │ │ ├── utils │ │ │ ├── Extensions.kt │ │ │ ├── FieldUtils.kt │ │ │ ├── GsonUtil.kt │ │ │ ├── JsonUtils.kt │ │ │ ├── LogUtil.kt │ │ │ ├── SimplifiedMethods.kt │ │ │ ├── StringExt.kt │ │ │ ├── VirtualFileExt.kt │ │ │ └── YamlHelper.kt │ │ │ └── workers │ │ │ ├── FileGenerator.kt │ │ │ └── Initializer.kt │ └── resources │ │ ├── META-INF │ │ ├── plugin.xml │ │ └── pluginIcon.svg │ │ ├── icons │ │ └── action.png │ │ └── messages │ │ └── MyBundle.properties └── test │ ├── kotlin │ └── com │ │ └── github │ │ └── zhangruiyu │ │ └── flutterjsonbeanfactory │ │ └── MyPluginTest.kt │ └── testData │ └── rename │ ├── foo.xml │ └── foo_after.xml ├── test_data.json └── wechat_pay.png /.github/beantojson_factory.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/FlutterJsonBeanFactory/f25572e8ba9ba9158a0243c4aa2d67336b4dbbad/.github/beantojson_factory.gif -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration: 2 | # https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | # Maintain dependencies for Gradle dependencies 7 | - package-ecosystem: "gradle" 8 | directory: "/" 9 | target-branch: "next" 10 | schedule: 11 | interval: "daily" 12 | # Maintain dependencies for GitHub Actions 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | target-branch: "next" 16 | schedule: 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/jetbrains-variant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/FlutterJsonBeanFactory/f25572e8ba9ba9158a0243c4aa2d67336b4dbbad/.github/jetbrains-variant.png -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions Workflow created for testing and preparing the plugin release in following steps: 2 | # - validate Gradle Wrapper, 3 | # - run test and verifyPlugin tasks, 4 | # - run buildPlugin task and prepare artifact for the further tests, 5 | # - run IntelliJ Plugin Verifier, 6 | # - create a draft release. 7 | # 8 | # Workflow is triggered on push and pull_request events. 9 | # 10 | # Docs: 11 | # - GitHub Actions: https://help.github.com/en/actions 12 | # - IntelliJ Plugin Verifier GitHub Action: https://github.com/ChrisCarini/intellij-platform-plugin-verifier-action 13 | # 14 | ## JBIJPPTPL 15 | 16 | name: Build 17 | on: 18 | # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g. for dependabot pull requests) 19 | push: 20 | branches: [main] 21 | # Trigger the workflow on any pull request 22 | pull_request: 23 | 24 | jobs: 25 | 26 | # Run Gradle Wrapper Validation Action to verify the wrapper's checksum 27 | gradleValidation: 28 | name: Gradle Wrapper 29 | runs-on: ubuntu-latest 30 | steps: 31 | 32 | # Check out current repository 33 | - name: Fetch Sources 34 | uses: actions/checkout@v2.3.4 35 | 36 | # Validate wrapper 37 | - name: Gradle Wrapper Validation 38 | uses: gradle/wrapper-validation-action@v1.0.4 39 | 40 | # Run verifyPlugin and test Gradle tasks 41 | test: 42 | name: Test 43 | needs: gradleValidation 44 | runs-on: ubuntu-latest 45 | steps: 46 | 47 | # Check out current repository 48 | - name: Fetch Sources 49 | uses: actions/checkout@v2.3.4 50 | 51 | # Setup Java 11 environment for the next steps 52 | - name: Setup Java 53 | uses: actions/setup-java@v2 54 | with: 55 | distribution: zulu 56 | java-version: 11 57 | cache: gradle 58 | 59 | # Set environment variables 60 | - name: Export Properties 61 | id: properties 62 | shell: bash 63 | run: | 64 | PROPERTIES="$(./gradlew properties --console=plain -q)" 65 | IDE_VERSIONS="$(echo "$PROPERTIES" | grep "^pluginVerifierIdeVersions:" | base64)" 66 | 67 | echo "::set-output name=ideVersions::$IDE_VERSIONS" 68 | echo "::set-output name=pluginVerifierHomeDir::~/.pluginVerifier" 69 | 70 | # Cache Plugin Verifier IDEs 71 | - name: Setup Plugin Verifier IDEs Cache 72 | uses: actions/cache@v2.1.6 73 | with: 74 | path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides 75 | key: ${{ runner.os }}-plugin-verifier-${{ steps.properties.outputs.ideVersions }} 76 | 77 | # Run Qodana inspections 78 | - name: Qodana - Code Inspection 79 | uses: JetBrains/qodana-action@v2.1-eap 80 | 81 | # Run tests 82 | - name: Run Tests 83 | run: ./gradlew test 84 | 85 | # Run verifyPlugin Gradle task 86 | - name: Verify Plugin 87 | run: ./gradlew verifyPlugin 88 | 89 | # Run IntelliJ Plugin Verifier action using GitHub Action 90 | - name: Run Plugin Verifier 91 | run: ./gradlew runPluginVerifier -Pplugin.verifier.home.dir=${{ steps.properties.outputs.pluginVerifierHomeDir }} 92 | 93 | # Build plugin with buildPlugin Gradle task and provide the artifact for the next workflow jobs 94 | # Requires test job to be passed 95 | build: 96 | name: Build 97 | needs: test 98 | runs-on: ubuntu-latest 99 | outputs: 100 | version: ${{ steps.properties.outputs.version }} 101 | changelog: ${{ steps.properties.outputs.changelog }} 102 | steps: 103 | 104 | # Check out current repository 105 | - name: Fetch Sources 106 | uses: actions/checkout@v2.3.4 107 | 108 | # Setup Java 11 environment for the next steps 109 | - name: Setup Java 110 | uses: actions/setup-java@v2 111 | with: 112 | distribution: zulu 113 | java-version: 11 114 | cache: gradle 115 | 116 | # Set environment variables 117 | - name: Export Properties 118 | id: properties 119 | shell: bash 120 | run: | 121 | PROPERTIES="$(./gradlew properties --console=plain -q)" 122 | VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')" 123 | NAME="$(echo "$PROPERTIES" | grep "^pluginName:" | cut -f2- -d ' ')" 124 | CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)" 125 | CHANGELOG="${CHANGELOG//'%'/'%25'}" 126 | CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" 127 | CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" 128 | 129 | echo "::set-output name=version::$VERSION" 130 | echo "::set-output name=name::$NAME" 131 | echo "::set-output name=changelog::$CHANGELOG" 132 | 133 | # Build artifact using buildPlugin Gradle task 134 | - name: Build Plugin 135 | run: ./gradlew buildPlugin 136 | 137 | # Store built plugin as an artifact for downloading 138 | - name: Upload artifacts 139 | uses: actions/upload-artifact@v2.2.4 140 | with: 141 | name: "${{ steps.properties.outputs.name }} - ${{ steps.properties.outputs.version }}" 142 | path: ./build/distributions/* 143 | 144 | # Prepare a draft release for GitHub Releases page for the manual verification 145 | # If accepted and published, release workflow would be triggered 146 | releaseDraft: 147 | name: Release Draft 148 | if: github.event_name != 'pull_request' 149 | needs: build 150 | runs-on: ubuntu-latest 151 | steps: 152 | 153 | # Check out current repository 154 | - name: Fetch Sources 155 | uses: actions/checkout@v2.3.4 156 | 157 | # Remove old release drafts by using the curl request for the available releases with draft flag 158 | - name: Remove Old Release Drafts 159 | env: 160 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 161 | run: | 162 | gh api repos/{owner}/{repo}/releases \ 163 | --jq '.[] | select(.draft == true) | .id' \ 164 | | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{} 165 | 166 | # Create new release draft - which is not publicly visible and requires manual acceptance 167 | - name: Create Release Draft 168 | env: 169 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 170 | run: | 171 | gh release create v${{ needs.build.outputs.version }} \ 172 | --draft \ 173 | --title "v${{ needs.build.outputs.version }}" \ 174 | --notes "${{ needs.build.outputs.changelog }}" 175 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions Workflow created for handling the release process based on the draft release prepared 2 | # with the Build workflow. Running the publishPlugin task requires the PUBLISH_TOKEN secret provided. 3 | 4 | name: Release 5 | on: 6 | release: 7 | types: [prereleased, released] 8 | 9 | jobs: 10 | 11 | # Prepare and publish the plugin to the Marketplace repository 12 | release: 13 | name: Publish Plugin 14 | runs-on: ubuntu-latest 15 | steps: 16 | 17 | # Check out current repository 18 | - name: Fetch Sources 19 | uses: actions/checkout@v2.3.4 20 | with: 21 | ref: ${{ github.event.release.tag_name }} 22 | 23 | # Setup Java 11 environment for the next steps 24 | - name: Setup Java 25 | uses: actions/setup-java@v2 26 | with: 27 | distribution: zulu 28 | java-version: 11 29 | cache: gradle 30 | 31 | # Update Unreleased section with the current release note 32 | - name: Patch Changelog 33 | run: | 34 | ./gradlew patchChangelog --release-note="`cat << EOM 35 | ${{ github.event.release.body }} 36 | EOM`" 37 | 38 | # Publish the plugin to the Marketplace 39 | - name: Publish Plugin 40 | env: 41 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} 42 | run: ./gradlew publishPlugin 43 | 44 | # Upload artifact as a release asset 45 | - name: Upload Release Asset 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | run: gh release upload ${{ github.event.release.tag_name }} ./build/distributions/* 49 | 50 | # Create pull request 51 | - name: Create Pull Request 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | run: | 55 | VERSION="${{ github.event.release.tag_name }}" 56 | BRANCH="changelog-update-$VERSION" 57 | 58 | git config user.email "action@github.com" 59 | git config user.name "GitHub Action" 60 | 61 | git checkout -b $BRANCH 62 | git commit -am "Changelog update - $VERSION" 63 | git push --set-upstream origin $BRANCH 64 | 65 | gh pr create \ 66 | --title "Changelog update - \`$VERSION\`" \ 67 | --body "Current pull request contains patched \`CHANGELOG.md\` file for the \`$VERSION\` version." \ 68 | --base main \ 69 | --head $BRANCH 70 | -------------------------------------------------------------------------------- /.github/workflows/run-ui-tests.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions Workflow created for launching UI tests on Linux, Windows, and Mac in the following steps: 2 | # - prepare and launch Idea with your plugin and robot-server plugin, which is need to interact with UI 3 | # - wait for the Idea started 4 | # - run UI tests with separate Gradle task 5 | # 6 | # Please check https://github.com/JetBrains/intellij-ui-test-robot for information about UI tests with IntelliJ IDEA. 7 | # 8 | # Workflow is triggered manually. 9 | 10 | name: Run UI Tests 11 | on: 12 | workflow_dispatch 13 | 14 | jobs: 15 | 16 | testUI: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | include: 22 | - os: ubuntu-latest 23 | runIde: | 24 | export DISPLAY=:99.0 25 | Xvfb -ac :99 -screen 0 1920x1080x16 & 26 | gradle runIdeForUiTests & 27 | - os: windows-latest 28 | runIde: start gradlew.bat runIdeForUiTests 29 | - os: macos-latest 30 | runIde: ./gradlew runIdeForUiTests & 31 | 32 | steps: 33 | 34 | # Check out current repository 35 | - name: Fetch Sources 36 | uses: actions/checkout@v2.3.4 37 | 38 | # Setup Java 11 environment for the next steps 39 | - name: Setup Java 40 | uses: actions/setup-java@v2 41 | with: 42 | distribution: zulu 43 | java-version: 11 44 | cache: gradle 45 | 46 | # Run IDEA prepared for UI testing 47 | - name: Run IDE 48 | run: ${{ matrix.runIde }} 49 | 50 | # Wait for IDEA to be started 51 | - name: Health Check 52 | uses: jtalk/url-health-check-action@v1.5 53 | with: 54 | url: http://127.0.0.1:8082 55 | max-attempts: 15 56 | retry-delay: 30s 57 | 58 | # Run tests 59 | - name: Tests 60 | run: ./gradlew test 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | .qodana 4 | build 5 | -------------------------------------------------------------------------------- /.run/Run IDE for UI Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 15 | 17 | true 18 | true 19 | false 20 | 21 | 22 | -------------------------------------------------------------------------------- /.run/Run IDE with Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.run/Run Plugin Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.run/Run Plugin Verification.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 25 | 26 | -------------------------------------------------------------------------------- /.run/Run Qodana.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 16 | 19 | 21 | true 22 | true 23 | false 24 | 25 | 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # FlutterJsonBeanFactory Changelog 4 | 5 | ## Unreleased 6 | 7 | ## 5.2.4 8 | 9 | ### Changed 10 | 11 | - late field is more intelligent 12 | 13 | ## 5.2.2 14 | 15 | ### Changed 16 | 17 | - double quotation marks are changed to single quotation marks 18 | 19 | ## 5.2.1 20 | 21 | ### Changed 22 | 23 | - fix [issues/167](https://github.com/fluttercandies/FlutterJsonBeanFactory/issues/167) 24 | 25 | ## 5.2.0 26 | 27 | ### Changed 28 | 29 | - update gradle and kotlin 30 | - fix idea version low version problem 31 | 32 | ## 5.1.7 33 | 34 | ### Changed 35 | 36 | - fixed conflicts with freezed 37 | 38 | ## 5.1.6 39 | 40 | ### Changed 41 | 42 | - Fix issues-161, map type conversion issue 43 | 44 | ## 5.1.5 45 | 46 | - support nested key,such as: @JSONField(name:"login.user.name") 47 | 48 | ## 5.1.3 49 | 50 | ### Changed 51 | 52 | - fix idea 2023.3.2 checkbox not fond exception 53 | 54 | ## 5.1.2 55 | 56 | ### Changed 57 | 58 | - After adding analysis, the type error problem is resolved 59 | - add copyWith annotation 60 | - pr: https://github.com/fluttercandies/FlutterJsonBeanFactory/pull/151 61 | - When there is no field in the class, the copyWith generation error occurs 62 | - Support default value(list) 63 | - Fixed an issue where hot reload would not refresh convertFuncMap after adding the model class 64 | - copyWith private field bugfix 65 | 66 | ## 5.0.3 67 | 68 | ### Changed 69 | 70 | - copyWith move to .g.dart 71 | 72 | ## 5.0.0 73 | 74 | ### Changed 75 | 76 | - supper map list> 77 | - custom generated path 78 | 79 | ## 4.5.5 80 | 81 | ### Changed 82 | 83 | - fix [issues/142](https://github.com/fluttercandies/FlutterJsonBeanFactory/issues/142) 84 | 85 | ## 4.5.3 86 | 87 | ### Changed 88 | 89 | - asObj bugfix 90 | 91 | ## 4.5.0 92 | 93 | ### Changed 94 | 95 | - copyWith Optional 96 | - toJson fromJson can be generated quickly 97 | 98 | ## 4.4.9 99 | 100 | ### Changed 101 | 102 | - add copyWith method 103 | - enum parse support 104 | 105 | ## 4.4.6 106 | 107 | ### Changed 108 | 109 | - asT null bugfix 110 | 111 | ## 4.5.2 112 | 113 | ### Changed 114 | 115 | - Support default value(string,int,bool),😭next version will support map,list 116 | 117 | ## 4.4.1 118 | 119 | ### Changed 120 | 121 | - Support map and set- 122 | 123 | ## 4.3.6 124 | 125 | ### Changed 126 | 127 | - Faster generation 128 | 129 | ## 4.3.5 130 | 131 | ### Changed 132 | 133 | - .g.art does not generate problem fixes 134 | 135 | ## 4.3.4 136 | 137 | ### Changed 138 | 139 | - remove the if judgment to get generic instances and use Map instead 140 | 141 | ### Added 142 | 143 | - add format json button 144 | 145 | ## 4.3.3 146 | 147 | ### Fixed 148 | 149 | - dynamic type optimization 150 | 151 | ## 4.3.2 152 | 153 | ### Fixed 154 | 155 | - A single file error does not affect other generated and show error file 156 | 157 | ## 4.3.0 158 | 159 | ### Fixed 160 | 161 | - fix static final will not be generated 162 | 163 | ## 4.2.7 164 | 165 | ### Added 166 | 167 | - Make your code more formal 168 | 169 | ## 4.2.5 170 | 171 | ### Added 172 | 173 | - When going to int,double is also available 174 | 175 | ## 4.2.3 176 | 177 | ### Added 178 | 179 | - new map modified to {} 180 | 181 | ## 4.2.2 182 | 183 | ### Added 184 | 185 | - break changer,only support null safety 186 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @zhangruiyu 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2020] [AlexV525] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlutterJsonBeanFactory 2 | 3 | Hi,Welcome to come to see me! 4 | What I do is generate dart beans based on json, as well as generics parameters and json build instances 5 | 6 | Language: English | [中文(qq群963752388)](https://juejin.cn/post/7030739002969817118/) 7 | 8 | 打扰:我想找份flutter开发的工作,请问有没有大佬可以帮忙推荐一下,谢谢了,我的邮箱:157418979@qq.com,安卓ios都会撸,不接受做马甲包的工作 9 | 打扰:顺便接flutter外包 10 | 11 | 12 | ### Easy Use 插件交流群qq(963752388) 13 | 14 | ![image](.github/beantojson_factory.gif) 15 | 16 | ## Known issue 17 | 18 | - If "No classes that inherit JsonConvert were found" is displayed, delete the ". Idea "directory under the project and 19 | click" invalidate Caches"in your (Andorid Studio or IDEA) button to restart the IDE 20 | 21 | ## Template ToDo list 22 | 23 | - [x] Support for instantiation through generics 24 | - [x] Support customized JSON parsing 25 | - [x] The supported types are: int double String datetime dynamic var, and List of the above types 26 | - [x] Two (and more)-dimensional array is supported (v4.5.6~) 27 | - [x] Support custom generated path 28 | 29 | 30 | 31 | ### Usage 32 | 33 | * 打扰:我想找份flutter开发的工作,请问有没有大佬可以帮忙推荐一下,谢谢了,我的邮箱:157418979@qq.com 34 | * Settings/Preferences > Plugins > Marketplace > Search for " 35 | FlutterJsonBeanFactory" > 36 | Install Plugin 37 | * Restart your Develop tools 38 | * Modify in the YAML file of the Flutter project 39 | 40 | * Press shortcut key `alt ` + `j` for mac , right click on package -> `New`->`Dart bean clas file from JSON`And Then you 41 | will know how to use 42 | * If you change the fields in the class, just press the shortcut alt + j to regenerate the tojson and fromjson methods. 43 | The generated method regenerates all helper classes and JsonConvert classes (the same as the shortcut alt + j) each 44 | time an entity file is created in the generated/json directory. 45 | * If you need generic conversions in your network requests, use the JsonConvert.fromJsonAsT method directly. 46 | * If no helper files are generated, you can delete the .idea directory and restart your idea 47 | * You can customize the JSON parsing scheme 48 | 49 | ```dart 50 | import 'generated/json/base/json_convert_content.dart'; 51 | 52 | class MyJsonConvert extends JsonConvert { 53 | T? asT(dynamic value) { 54 | try { 55 | String type = T.toString(); 56 | if (type == "DateTime") { 57 | return DateFormat("dd.MM.yyyy").parse(value) as T; 58 | } else { 59 | return super.asT(value); 60 | } 61 | } catch (e, stackTrace) { 62 | print('asT<$T> $e $stackTrace'); 63 | return null; 64 | } 65 | } 66 | } 67 | 68 | Future main() async { 69 | jsonConvert = MyJsonConvert(); 70 | runApp(Text("OK")); 71 | } 72 | ``` 73 | 74 | custom generated path->(pubspec.yaml) 75 | 76 | ```yaml 77 | flutter_json: 78 | generated_path: src/json/** 79 | ``` 80 | 81 | 82 | 83 | 84 | ### 开源不易,觉得有用的话可以请作者喝杯冰可乐🥤 85 | 86 | 打赏 87 | 88 | ### 赞助列表(非常非常的感谢,仅记录此插件收到的打赏,承诺将收到的所有赞助用于购买可乐和赞助其他开源作者) 89 | 90 | 91 | 92 | 93 | 96 | 99 | 102 | 103 | 104 | 107 | 110 | 113 | 114 | 115 | 118 | 121 | 124 | 125 | 126 | 129 | 132 | 135 | 136 | 137 | 140 | 143 | 146 | 147 | 150 | 153 | 156 | 157 | 158 | 161 | 164 | 167 | 168 | 169 | 172 | 175 | 178 | 179 | 180 | 183 | 186 | 189 | 190 | 191 | 192 |
94 | 名称
95 |
97 | 金额
98 |
100 | 时间
101 |
105 | 微信:BeADre
106 |
108 | 18.8元
109 |
111 | 2025年3月4日
112 |
116 | 微信:大熊猫🐱
117 |
119 | 20元
120 |
122 | 2024年4月3日
123 |
127 | qq:sunny
128 |
130 | 10元
131 |
133 | 2023年12月21日
134 |
138 | 微信:未知
139 |
141 | 10元
142 |
144 | 2023年11月17日
145 |
148 | QQ:郭嘉
149 |
151 | 10元 152 | 154 | 2023年09月12日 155 |
159 | QQ:初一
160 |
162 | 100元 163 | 165 | 2023年08月15日 166 |
170 | Github:cr1992
171 |
173 | 6.66元 174 | 176 | 2023年08月4日 177 |
181 | QQ:余军
182 |
184 | 200元 185 | 187 | 2022年12月 188 |
193 | 194 | ### Find me useful ? :heart: 195 | 196 | * Support me by clicking the :star: button on the upper right of this page. :v: 197 | * Spread to others to let more people have a better develope expierience :heart: 198 | 199 | --- 200 | Thanks to [JetBrains](https://www.jetbrains.com/?from=fluttercandies) for allocating free open-source licenses for IDEs 201 | such as [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=fluttercandies). 202 | 203 | [](https://www.jetbrains.com/?from=fluttercandies) -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.changelog.Changelog 2 | import org.jetbrains.changelog.markdownToHTML 3 | import org.jetbrains.intellij.platform.gradle.TestFrameworkType 4 | 5 | plugins { 6 | id("java") // Java support 7 | alias(libs.plugins.kotlin) // Kotlin support 8 | alias(libs.plugins.intelliJPlatform) // IntelliJ Platform Gradle Plugin 9 | alias(libs.plugins.changelog) // Gradle Changelog Plugin 10 | alias(libs.plugins.qodana) // Gradle Qodana Plugin 11 | alias(libs.plugins.kover) // Gradle Kover Plugin 12 | } 13 | 14 | group = providers.gradleProperty("pluginGroup").get() 15 | version = providers.gradleProperty("pluginVersion").get() 16 | 17 | // Set the JVM language level used to build the project. 18 | kotlin { 19 | jvmToolchain(17) 20 | } 21 | 22 | // Configure project's dependencies 23 | repositories { 24 | mavenCentral() 25 | 26 | // IntelliJ Platform Gradle Plugin Repositories Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-repositories-extension.html 27 | intellijPlatform { 28 | defaultRepositories() 29 | } 30 | google() 31 | } 32 | 33 | // Configure Gradle IntelliJ Plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin 34 | /*intellij { 35 | pluginName.set(properties("pluginName")) 36 | version.set(properties("platformVersion")) 37 | type.set(properties("platformType")) 38 | downloadSources.set(properties("platformDownloadSources").toBoolean()) 39 | updateSinceUntilBuild.set(true) 40 | 41 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file. 42 | plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty)) 43 | }*/ 44 | 45 | dependencies { 46 | testImplementation(libs.junit) 47 | 48 | // IntelliJ Platform Gradle Plugin Dependencies Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html 49 | intellijPlatform { 50 | create(providers.gradleProperty("platformType"), providers.gradleProperty("platformVersion")) 51 | 52 | // Plugin Dependencies. Uses `platformBundledPlugins` property from the gradle.properties file for bundled IntelliJ Platform plugins. 53 | bundledPlugins(providers.gradleProperty("platformBundledPlugins").map { it.split(',') }) 54 | 55 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file for plugin from JetBrains Marketplace. 56 | plugins(providers.gradleProperty("platformPlugins").map { it.split(',') }) 57 | 58 | instrumentationTools() 59 | pluginVerifier() 60 | zipSigner() 61 | testFramework(TestFrameworkType.Platform) 62 | } 63 | // implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.9.25") 64 | implementation("org.apache.commons:commons-lang3:3.12.0") 65 | } 66 | 67 | 68 | 69 | // Configure IntelliJ Platform Gradle Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html 70 | intellijPlatform { 71 | pluginConfiguration { 72 | version = providers.gradleProperty("pluginVersion") 73 | 74 | // Extract the section from README.md and provide for the plugin's manifest 75 | description = providers.fileContents(layout.projectDirectory.file("README.md")).asText.map { 76 | val start = "" 77 | val end = "" 78 | 79 | with(it.lines()) { 80 | if (!containsAll(listOf(start, end))) { 81 | throw GradleException("Plugin description section not found in README.md:\n$start ... $end") 82 | } 83 | subList(indexOf(start) + 1, indexOf(end)).joinToString("\n").let(::markdownToHTML) 84 | } 85 | } 86 | 87 | val changelog = project.changelog // local variable for configuration cache compatibility 88 | // Get the latest available change notes from the changelog file 89 | changeNotes = providers.gradleProperty("pluginVersion").map { pluginVersion -> 90 | with(changelog) { 91 | renderItem( 92 | (getOrNull(pluginVersion) ?: getUnreleased()) 93 | .withHeader(false) 94 | .withEmptySections(false), 95 | Changelog.OutputType.HTML, 96 | ) 97 | } 98 | } 99 | 100 | ideaVersion { 101 | sinceBuild = providers.gradleProperty("pluginSinceBuild") 102 | untilBuild = providers.gradleProperty("pluginUntilBuild") 103 | } 104 | } 105 | 106 | signing { 107 | certificateChain = providers.environmentVariable("CERTIFICATE_CHAIN") 108 | privateKey = providers.environmentVariable("PRIVATE_KEY") 109 | password = providers.environmentVariable("PRIVATE_KEY_PASSWORD") 110 | } 111 | 112 | publishing { 113 | token = providers.environmentVariable("ORG_GRADLE_PROJECT_intellijPublishToken") 114 | // The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3 115 | // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more: 116 | // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel 117 | channels = providers.gradleProperty("pluginVersion").map { listOf(it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" }) } 118 | } 119 | 120 | pluginVerification { 121 | ides { 122 | recommended() 123 | } 124 | } 125 | } 126 | 127 | // Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin 128 | changelog { 129 | groups.empty() 130 | repositoryUrl = providers.gradleProperty("pluginRepositoryUrl") 131 | } 132 | 133 | // Configure Gradle Kover Plugin - read more: https://github.com/Kotlin/kotlinx-kover#configuration 134 | kover { 135 | reports { 136 | total { 137 | xml { 138 | onCheck = true 139 | } 140 | } 141 | } 142 | } 143 | 144 | tasks { 145 | wrapper { 146 | gradleVersion = providers.gradleProperty("gradleVersion").get() 147 | } 148 | 149 | publishPlugin { 150 | dependsOn(patchChangelog) 151 | } 152 | } 153 | 154 | intellijPlatformTesting { 155 | runIde { 156 | register("runIdeForUiTests") { 157 | task { 158 | jvmArgumentProviders += CommandLineArgumentProvider { 159 | listOf( 160 | "-Drobot-server.port=8082", 161 | "-Dide.mac.message.dialogs.as.sheets=false", 162 | "-Djb.privacy.policy.text=", 163 | "-Djb.consents.confirmation.enabled=false", 164 | ) 165 | } 166 | } 167 | 168 | plugins { 169 | robotServerPlugin() 170 | } 171 | } 172 | } 173 | } 174 | 175 | /* 176 | 177 | tasks { 178 | // Set the JVM compatibility versions 179 | properties("javaVersion").let { 180 | withType { 181 | sourceCompatibility = it 182 | targetCompatibility = it 183 | } 184 | withType { 185 | kotlinOptions.jvmTarget = it 186 | } 187 | } 188 | 189 | wrapper { 190 | gradleVersion = properties("gradleVersion") 191 | } 192 | 193 | patchPluginXml { 194 | version.set(properties("pluginVersion")) 195 | sinceBuild.set(properties("pluginSinceBuild")) 196 | untilBuild.set(properties("pluginUntilBuild")) 197 | 198 | // Extract the section from README.md and provide for the plugin's manifest 199 | pluginDescription.set( 200 | projectDir.resolve("README.md").readText().lines().run { 201 | val start = "" 202 | val end = "" 203 | 204 | if (!containsAll(listOf(start, end))) { 205 | throw GradleException("Plugin description section not found in README.md:\n$start ... $end") 206 | } 207 | subList(indexOf(start) + 1, indexOf(end)) 208 | }.joinToString("\n").run { markdownToHTML(this) } 209 | ) 210 | 211 | // Get the latest available change notes from the changelog file 212 | changeNotes.set(provider { 213 | changelog.run { 214 | getOrNull(properties("pluginVersion")) ?: getLatest() 215 | }.toHTML() 216 | }) 217 | } 218 | 219 | runPluginVerifier { 220 | ideVersions.set(properties("pluginVerifierIdeVersions").split(',').map(String::trim).filter(String::isNotEmpty)) 221 | } 222 | 223 | // Configure UI tests plugin 224 | // Read more: https://github.com/JetBrains/intellij-ui-test-robot 225 | runIdeForUiTests { 226 | systemProperty("robot-server.port", "8082") 227 | systemProperty("ide.mac.message.dialogs.as.sheets", "false") 228 | systemProperty("jb.privacy.policy.text", "") 229 | systemProperty("jb.consents.confirmation.enabled", "false") 230 | } 231 | 232 | signPlugin { 233 | certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) 234 | privateKey.set(System.getenv("PRIVATE_KEY")) 235 | password.set(System.getenv("PRIVATE_KEY_PASSWORD")) 236 | } 237 | 238 | publishPlugin { 239 | dependsOn("patchChangelog") 240 | token.set(System.getenv("ORG_GRADLE_PROJECT_intellijPublishToken")) 241 | // pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3 242 | // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more: 243 | // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel 244 | channels.set(listOf(properties("pluginVersion").split('-').getOrElse(1) { "default" }.split('.').first())) 245 | } 246 | } 247 | */ 248 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # IntelliJ Platform Artifacts Repositories 2 | # -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html 3 | org.gradle.caching=true 4 | pluginGroup = org.ruiyu 5 | pluginName = FlutterJsonBeanFactory 6 | pluginVersion = 5.2.4 7 | 8 | # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html 9 | # for insight into build numbers and IntelliJ Platform versions. 10 | pluginSinceBuild = 232 11 | pluginUntilBuild = 243.* 12 | 13 | # Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl 14 | # See https://jb.gg/intellij-platform-builds-list for available build versions. 15 | pluginVerifierIdeVersions = 2023.2.1 16 | 17 | platformType = IC 18 | platformVersion = 2023.2.1 19 | #platformVersion = 2024.2 20 | platformDownloadSources = true 21 | 22 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html 23 | # Example: platformPlugins = com.jetbrains.php:203.4449.22, org.intellij.scala:2023.3.27@EAP 24 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 25 | #platformPlugins = yaml, java, Dart:203.7759, io.flutter:61.2.2, Kotlin 26 | #platformPlugins = Dart:241.17502, io.flutter:80.0.2 27 | platformPlugins = Dart:232.9559.10, io.flutter:76.3.4 28 | platformBundledPlugins = com.intellij.java, org.jetbrains.plugins.yaml, org.jetbrains.kotlin, org.intellij.intelliLang 29 | 30 | gradleVersion = 8.9 31 | 32 | # Opt-out flag for bundling Kotlin standard library. 33 | # See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details. 34 | # suppress inspection "UnusedProperty" 35 | kotlin.stdlib.default.dependency = false 36 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html 37 | org.gradle.configuration-cache = true -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | # libraries 3 | junit = "4.13.2" 4 | 5 | # plugins 6 | changelog = "2.2.1" 7 | intelliJPlatform = "2.0.1" 8 | kotlin = "1.9.25" 9 | kover = "0.8.3" 10 | qodana = "2024.1.9" 11 | 12 | [libraries] 13 | junit = { group = "junit", name = "junit", version.ref = "junit" } 14 | 15 | [plugins] 16 | changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } 17 | intelliJPlatform = { id = "org.jetbrains.intellij.platform", version.ref = "intelliJPlatform" } 18 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 19 | kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } 20 | qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" } 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/FlutterJsonBeanFactory/f25572e8ba9ba9158a0243c4aa2d67336b4dbbad/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /qodana.yml: -------------------------------------------------------------------------------- 1 | # Qodana configuration: 2 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html 3 | 4 | version: 1.0 5 | profile: 6 | name: qodana.recommended 7 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "FlutterJsonBeanFactory" 2 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/Flag.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory 2 | 3 | const val PLUGIN_NAME = "FlutterBeanFactory" -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/action/class_body_generate/DartGenerateCopyAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.action.class_body_generate 2 | 3 | import com.jetbrains.lang.dart.ide.generation.BaseDartGenerateAction 4 | import com.jetbrains.lang.dart.ide.generation.BaseDartGenerateHandler 5 | 6 | 7 | class DartGenerateToFromJsonAction : BaseDartGenerateAction() { 8 | 9 | override fun getGenerateHandler(): BaseDartGenerateHandler { 10 | return DartGenerateToFromJsonHandler() 11 | } 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/action/class_body_generate/DartGenerateCopyFix.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.action.class_body_generate 2 | 3 | import com.intellij.codeInsight.template.Template 4 | import com.intellij.codeInsight.template.TemplateManager 5 | import com.intellij.codeInsight.template.impl.TextExpression 6 | import com.intellij.openapi.editor.Editor 7 | import com.intellij.openapi.project.Project 8 | import com.jetbrains.lang.dart.ide.generation.BaseCreateMethodsFix 9 | import com.jetbrains.lang.dart.psi.DartClass 10 | import com.jetbrains.lang.dart.psi.DartComponent 11 | 12 | open class DartGenerateToFromJsonFix(dartClass: DartClass) : BaseCreateMethodsFix(dartClass) { 13 | 14 | override fun processElements(project: Project, editor: Editor, elementsToProcess: Set) { 15 | val templateManager = TemplateManager.getInstance(project) 16 | this.anchor = this.doAddMethodsForOne( 17 | editor, 18 | templateManager, 19 | this.buildFunctionsText(templateManager, elementsToProcess), 20 | this.anchor 21 | ) 22 | } 23 | 24 | override fun getNothingFoundMessage(): String { 25 | return "" 26 | } 27 | 28 | private fun buildFunctionsText( 29 | templateManager: TemplateManager, 30 | elementsToProcess: Set 31 | ): Template { 32 | val template = templateManager.createTemplate(this.javaClass.name, "toFromJson") 33 | template.isToReformat = true 34 | template.addVariable(TextExpression("\tfactory ${this.myDartClass.name!!}.fromJson(Map json) => $${this.myDartClass.name!!}FromJson(json);"), true) 35 | template.addTextSegment("\n") 36 | template.addVariable(TextExpression("\tMap toJson() => $${this.myDartClass.name!!}ToJson(this);"), true) 37 | template.addTextSegment("\n") 38 | return template 39 | } 40 | 41 | override fun buildFunctionsText(templateManager: TemplateManager, e: DartComponent): Template? { 42 | return null 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/action/class_body_generate/DartGenerateCopyHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.action.class_body_generate 2 | 3 | import com.jetbrains.lang.dart.ide.generation.BaseCreateMethodsFix 4 | import com.jetbrains.lang.dart.ide.generation.BaseDartGenerateHandler 5 | import com.jetbrains.lang.dart.psi.DartClass 6 | import com.jetbrains.lang.dart.psi.DartComponent 7 | 8 | class DartGenerateToFromJsonHandler : BaseDartGenerateHandler() { 9 | 10 | override fun getTitle(): String { 11 | return "Generate Name" 12 | } 13 | 14 | override fun createFix(dartClass: DartClass): BaseCreateMethodsFix<*> { 15 | if (dartClass == null) { 16 | // $$$reportNull$$$0(1); 17 | } 18 | 19 | val var10000 = DartGenerateToFromJsonFix(dartClass) 20 | if (var10000 == null) { 21 | // $$$reportNull$$$0(2); 22 | } 23 | 24 | return var10000 25 | } 26 | 27 | override fun collectCandidates(dartClass: DartClass, candidates: MutableList) { 28 | if (dartClass == null) { 29 | // $$$reportNull$$$0(3); 30 | } 31 | 32 | if (candidates == null) { 33 | // $$$reportNull$$$0(4); 34 | } 35 | 36 | } 37 | 38 | override fun doAllowEmptySelection(): Boolean { 39 | return true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/action/dart_to_helper/FlutterBeanFactoryAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.action.dart_to_helper 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.actionSystem.PlatformDataKeys 6 | import com.intellij.openapi.application.ApplicationManager 7 | import com.intellij.openapi.application.runWriteAction 8 | import com.intellij.openapi.project.Project 9 | import com.intellij.openapi.vfs.newvfs.impl.VirtualFileImpl 10 | import com.github.zhangruiyu.flutterjsonbeanfactory.file.FileHelpers 11 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.YamlHelper 12 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.commitContent 13 | import com.github.zhangruiyu.flutterjsonbeanfactory.workers.FileGenerator 14 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.showNotify 15 | import java.io.File 16 | import java.lang.RuntimeException 17 | 18 | class FlutterBeanFactoryAction : AnAction() { 19 | 20 | override fun actionPerformed(e: AnActionEvent) { 21 | generateAllFile(e.getData(PlatformDataKeys.PROJECT)!!) 22 | 23 | } 24 | 25 | companion object { 26 | /** 27 | * 生成辅助类 28 | */ 29 | fun generateAllFile(project: Project) { 30 | val pubSpecConfig = YamlHelper.getPubSpecConfig(project) 31 | //判断是否是flutter项目 32 | if (pubSpecConfig != null && YamlHelper.shouldActivateFor(project)) { 33 | try { 34 | //如果没有可以生成的文件,那么就不会生成 35 | val allClass = FileHelpers.getAllEntityFiles(project) 36 | if (allClass.isEmpty()) { 37 | throw RuntimeException("No classes that inherit JsonConvert were found,the project root directory must contain the lib directory, because this plugin will only scan the lib directory of the root directory") 38 | } 39 | ApplicationManager.getApplication().invokeLater { 40 | runWriteAction { 41 | FileGenerator(project).generate() 42 | } 43 | } 44 | FileHelpers.getGeneratedFileRun(project,pubSpecConfig.generatedPath) { 45 | //1.上次生成的老旧老文件 46 | val oldHelperChildren = 47 | it.children.filterIsInstance().toMutableList() 48 | //2.删除多余helper文件 49 | oldHelperChildren.forEach { oldPath -> 50 | //新生成的文件名 51 | val newFileNameList = 52 | allClass.map { newPath -> "${File(newPath.second).nameWithoutExtension}.g.dart" } 53 | //如果现在生成的不包含在这里,那么就删除 54 | if (newFileNameList.contains(oldPath.name).not()) { 55 | oldPath.delete(oldPath) 56 | } 57 | } 58 | //3.重新生成所有helper类 59 | FileHelpers.generateAllDartEntityHelper(project,pubSpecConfig.generatedPath, allClass) 60 | //4.重新生成jsonConvert类 61 | val content = StringBuilder() 62 | content.append("// ignore_for_file: non_constant_identifier_names\n// ignore_for_file: camel_case_types\n// ignore_for_file: prefer_single_quotes\n\n") 63 | content.append("// This file is automatically generated. DO NOT EDIT, all your changes would be lost.\n") 64 | content.append("import 'package:flutter/material.dart' show debugPrint;\n") 65 | //导包 66 | allClass.forEach { itemNeedFile -> 67 | content.append(itemNeedFile.second) 68 | content.append("\n") 69 | } 70 | 71 | content.append("\n") 72 | 73 | //// 74 | content.append("JsonConvert jsonConvert = JsonConvert();") 75 | content.append("\n") 76 | content.append("typedef JsonConvertFunction = T Function(Map json);") 77 | content.append("\n") 78 | content.append("typedef EnumConvertFunction = T Function(String value);") 79 | content.append("\n") 80 | content.append("typedef ConvertExceptionHandler = void Function(Object error, StackTrace stackTrace);") 81 | content.append("\n") 82 | content.append("extension MapSafeExt on Map {\n" + 83 | " T? getOrNull(K? key) {\n" + 84 | " if (!containsKey(key) || key == null) {\n" + 85 | " return null;\n" + 86 | " } else {\n" + 87 | " return this[key] as T?;\n" + 88 | " }\n" + 89 | " }\n" + 90 | "}") 91 | content.append("\n") 92 | content.append("class JsonConvert {") 93 | content.append("\n") 94 | content.append("\tstatic ConvertExceptionHandler? onError;") 95 | content.append("\n") 96 | ///这里本来写成get方法的,虽然解决了hotreload无法更新的问题,但是会导致效率低下,对jsonarray有很多值的情况下,遍历会导致重复get效率低下 97 | content.append("\tJsonConvertClassCollection convertFuncMap = JsonConvertClassCollection();") 98 | content.append("\t/// When you are in the development, to generate a new model class, hot-reload doesn't find new generation model class, you can build on MaterialApp method called jsonConvert. ReassembleConvertFuncMap (); This method only works in a development environment\n") 99 | content.append("\t/// https://flutter.cn/docs/development/tools/hot-reload\n") 100 | content.append("\t/// class MyApp extends StatelessWidget {\n") 101 | content.append("\t/// const MyApp({Key? key})\n") 102 | content.append("\t/// : super(key: key);\n") 103 | content.append("\t///\n") 104 | content.append("\t/// @override\n") 105 | content.append("\t/// Widget build(BuildContext context) {\n") 106 | content.append("\t/// jsonConvert.reassembleConvertFuncMap();\n") 107 | content.append("\t/// return MaterialApp();\n") 108 | content.append("\t/// }\n") 109 | content.append("\t/// }\n") 110 | content.append("\tvoid reassembleConvertFuncMap(){\n") 111 | content.append("\tbool isReleaseMode = const bool.fromEnvironment('dart.vm.product');\n") 112 | content.append("\tif(!isReleaseMode) {\n") 113 | content.append("\tconvertFuncMap = JsonConvertClassCollection();\n") 114 | content.append("\t}\n") 115 | content.append("\t}\n") 116 | content.append( 117 | " T? convert(dynamic value, {EnumConvertFunction? enumConvert}) {\n" + 118 | " if (value == null) {\n" + 119 | " return null;\n" + 120 | " }\n" + 121 | " if (value is T) {\n" + 122 | " return value;\n" + 123 | " }\n" + 124 | " try {\n" + 125 | " return _asT(value, enumConvert: enumConvert);\n" + 126 | " } catch (e, stackTrace) {\n" + 127 | " debugPrint('asT<${"\$T"}> ${"\$e"} ${"\$stackTrace"}');\n" + 128 | " if (onError != null) {" + 129 | " onError!(e, stackTrace);" + 130 | " }"+ 131 | " return null;\n" + 132 | " }\n" + 133 | " }" 134 | ) 135 | content.append("\n\n") 136 | content.append( 137 | " List? convertList(List? value, {EnumConvertFunction? enumConvert}) {\n" + 138 | " if (value == null) {\n" + 139 | " return null;\n" + 140 | " }\n" + 141 | " try {\n" + 142 | " return value.map((dynamic e) => _asT(e,enumConvert: enumConvert)).toList();\n" + 143 | " } catch (e, stackTrace) {\n" + 144 | " debugPrint('asT<${"\$T"}> ${"\$e"} ${"\$stackTrace"}');\n" + 145 | " if (onError != null) {" + 146 | " onError!(e, stackTrace);" + 147 | " }"+ 148 | " return [];\n" + 149 | " }\n" + 150 | " }" 151 | ) 152 | content.append("\n\n") 153 | content.append( 154 | "List? convertListNotNull(dynamic value, {EnumConvertFunction? enumConvert}) {\n" + 155 | " if (value == null) {\n" + 156 | " return null;\n" + 157 | " }\n" + 158 | " try {\n" + 159 | " return (value as List).map((dynamic e) => _asT(e,enumConvert: enumConvert)!).toList();\n" + 160 | " } catch (e, stackTrace) {\n" + 161 | " debugPrint('asT<${"\$T"}> ${"\$e"} ${"\$stackTrace"}');\n" + 162 | " if (onError != null) {" + 163 | " onError!(e, stackTrace);" + 164 | " }"+ 165 | " return [];\n" + 166 | " }\n" + 167 | " }" 168 | ) 169 | content.append("\n\n") 170 | content.append( 171 | " T? _asT(dynamic value,\n" + 172 | " {EnumConvertFunction? enumConvert}) {\n" + 173 | " final String type = T.toString();\n" + 174 | " final String valueS = value.toString();\n" + 175 | " if (enumConvert != null) {\n" + 176 | " return enumConvert(valueS) as T;\n" + 177 | " } else if (type == \"String\") {\n" + 178 | " return valueS as T;\n" + 179 | " } else if (type == \"int\") {\n" + 180 | " final int? intValue = int.tryParse(valueS);\n" + 181 | " if (intValue == null) {\n" + 182 | " return double.tryParse(valueS)?.toInt() as T?;\n" + 183 | " } else {\n" + 184 | " return intValue as T;\n" + 185 | " }\n" + 186 | " } else if (type == \"double\") {\n" + 187 | " return double.parse(valueS) as T;\n" + 188 | " } else if (type == \"DateTime\") {\n" + 189 | " return DateTime.parse(valueS) as T;\n" + 190 | " } else if (type == \"bool\") {\n" + 191 | " if (valueS == '0' || valueS == '1') {\n" + 192 | " return (valueS == '1') as T;\n" + 193 | " }\n" + 194 | " return (valueS == 'true') as T;\n" + 195 | " } else if (type == \"Map\" || type.startsWith(\"Map<\")) {\n" + 196 | " return value as T;\n" + 197 | " } else {\n" + 198 | " if (convertFuncMap.containsKey(type)) {\n" + 199 | " if (value == null) {\n" + 200 | " return null;\n" + 201 | " }\n" + 202 | " var covertFunc = convertFuncMap[type]!;\n" + 203 | " if(covertFunc is Map) {\n" + 204 | " return covertFunc(value as Map) as T;\n" + 205 | " }else{\n" + 206 | " return covertFunc(Map.from(value)) as T;\n" + 207 | " }\n" + 208 | " } else {\n" + 209 | " throw UnimplementedError('${"\$type"} unimplemented,you can try running the app again');\n" + 210 | " }\n" + 211 | " }\n" + 212 | " }" 213 | ) 214 | //_getListFromType 215 | content.append("\n\n") 216 | content.append( 217 | "\t//list is returned by type\n" + 218 | "\tstatic M? _getListChildType(List> data) {\n" 219 | ) 220 | allClass.forEach { itemClass -> 221 | itemClass.first.classes.forEach { itemFile -> 222 | content.append("\t\tif(<${itemFile.className}>[] is M){\n") 223 | content.append("\t\t\treturn data.map<${itemFile.className}>((Map e) => ${itemFile.className}.fromJson(e)).toList() as M;\n") 224 | content.append("\t\t}\n") 225 | } 226 | } 227 | content.append( 228 | "\n\t\tdebugPrint(\"\$M not found\");\n\t" 229 | ) 230 | content.append( 231 | "\n\t\treturn null;\n" 232 | ) 233 | content.append( 234 | "\t}" 235 | ) 236 | content.append("\n\n") 237 | //fromJsonAsT 238 | content.append( 239 | "\tstatic M? fromJsonAsT(dynamic json) {\n" + 240 | "\t\tif (json is M) {\n" + 241 | "\t\t\treturn json;\n" + 242 | "\t\t}\n" + 243 | "\t\tif (json is List) {\n" + 244 | "\t\t\treturn _getListChildType(json.map((dynamic e) => e as Map).toList());\n" + 245 | "\t\t} else {\n" + 246 | "\t\t\treturn jsonConvert.convert(json);\n" + 247 | "\t\t}\n" + 248 | "\t}" 249 | ) 250 | 251 | content.append("\n") 252 | content.append("}\n\n") 253 | content.append("\tclass JsonConvertClassCollection {") 254 | content.append("\tMap convertFuncMap = {") 255 | content.append("\n") 256 | allClass.forEach { itemClass -> 257 | itemClass.first.classes.forEach { itemFile -> 258 | content.append("\t\t(${itemFile.className}).toString(): ${itemFile.className}.fromJson,\n") 259 | } 260 | } 261 | content.append("\t};\n") 262 | content.append("bool containsKey(String type) {\n") 263 | content.append("return convertFuncMap.containsKey(type);\n") 264 | content.append("}\n") 265 | content.append("JsonConvertFunction? operator [](String key) {\n") 266 | content.append("return convertFuncMap[key];\n") 267 | content.append("}\n") 268 | content.append("\t}") 269 | val generated = FileHelpers.getJsonConvertBaseFile(project,pubSpecConfig.generatedPath) 270 | //获取json_convert_content目录,并写入 271 | generated.findOrCreateChildData(this, "json_convert_content.dart") 272 | .commitContent(project, content.toString()) 273 | 274 | project.showNotify("convert factory is generated") 275 | } 276 | } catch (e: RuntimeException) { 277 | e.printStackTrace() 278 | e.message?.let { project.showNotify(it) } 279 | } 280 | 281 | 282 | } else { 283 | project.showNotify("This project is not the flutter project") 284 | } 285 | 286 | 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/action/dart_to_helper/model/FieldInfo.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.action.dart_to_helper.model 2 | 3 | import com.intellij.lang.ASTNode 4 | import com.intellij.psi.impl.source.tree.CompositeElement 5 | import com.intellij.psi.impl.source.tree.LeafPsiElement 6 | import org.jetbrains.kotlin.psi.psiUtil.children 7 | 8 | 9 | data class FieldClassTypeInfo( 10 | ///主类型,List,int,String,Map,dynamic等 11 | val primaryType: String, 12 | val nullable: Boolean, 13 | ///泛型类型 14 | val genericityChildType: FieldClassTypeInfo? = null, 15 | val genericityString: String? = null, 16 | ) { 17 | fun isMap(): Boolean { 18 | return primaryType == "Map" 19 | } 20 | 21 | fun isList(): Boolean { 22 | return primaryType == "List" 23 | } 24 | 25 | fun isSet(): Boolean { 26 | return primaryType == "Set" 27 | } 28 | 29 | companion object { 30 | fun findChild(node: ASTNode): List { 31 | return if (node.textContains('<') || node.text.endsWith('?')) { 32 | val astNodes = node.children().toList() 33 | if (astNodes.size == 1) { 34 | findChild(astNodes.first()) 35 | } else { 36 | astNodes 37 | } 38 | } else { 39 | node.children().toList() 40 | } 41 | 42 | } 43 | 44 | fun parseFieldClassTypeInfo(typeList: List): FieldClassTypeInfo? { 45 | println("泛型的文案之前 ${typeList.joinToString { it.text }}") 46 | val filterTypeList = typeList.filterIsInstance() 47 | ///是否可以是null 48 | val canNull = typeList.filterIsInstance().firstOrNull()?.text == "?" 49 | println("泛型的文案去除不需要的后 ${filterTypeList.size} ${filterTypeList.joinToString { it.text }}") 50 | if (filterTypeList.isEmpty() || filterTypeList.size == 1) { 51 | val firstChild = filterTypeList.first() 52 | println("firstChild ${firstChild.text}") 53 | val firstChildChild = findChild(firstChild) 54 | if (firstChildChild.size > 1) { 55 | return parseFieldClassTypeInfo(firstChildChild) 56 | } else { 57 | println("泛型的文案个数1 ${firstChild.text}") 58 | return FieldClassTypeInfo(firstChild.text, canNull) 59 | } 60 | } else if (filterTypeList.size == 2) { 61 | // println("泛型的文案 ${toList[1].text}") 62 | // println("\t\t泛型的文案size $size") 63 | // println("\t\t泛型的文案size ${toList.joinToString { it.text }}") 64 | return FieldClassTypeInfo( 65 | filterTypeList.first().text, 66 | canNull, 67 | genericityChildType = parseFieldClassTypeInfo(findChild(filterTypeList[1])), 68 | genericityString = typeList[1].text 69 | ) 70 | } else if (filterTypeList.size == 3) { 71 | ///这里只会是2 父类型和泛型 72 | // println("泛型的文案 ${toList[1].text}") 73 | println("\t\t泛型的文案size ${filterTypeList.joinToString { it.text }}") 74 | println("\t\t泛型的文案size ${filterTypeList.joinToString { it::class.java.name }}") 75 | return null 76 | } else { 77 | return null 78 | } 79 | } 80 | } 81 | 82 | fun ASTNode.typeChild(): List { 83 | return children().filterIsInstance().toList() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/action/dart_to_helper/node/ClassGeneratorInfoModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.action.dart_to_helper.node 2 | 3 | import com.github.zhangruiyu.flutterjsonbeanfactory.action.dart_to_helper.model.FieldClassTypeInfo 4 | import com.github.zhangruiyu.flutterjsonbeanfactory.action.jsontodart.utils.* 5 | import com.github.zhangruiyu.flutterjsonbeanfactory.setting.Settings 6 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.toLowerCaseFirstOne 7 | import com.intellij.openapi.application.ApplicationManager 8 | 9 | 10 | /** 11 | * User: zhangruiyu 12 | * Date: 2019/12/23 13 | * Time: 11:32 14 | */ 15 | class HelperFileGeneratorInfo( 16 | val imports: MutableList = mutableListOf(), 17 | val classes: MutableList = mutableListOf() 18 | ) 19 | 20 | class HelperClassGeneratorInfo { 21 | //协助的类名 22 | lateinit var className: String 23 | private val fields: MutableList = mutableListOf() 24 | private val serializeFields get() = fields.filter { it.getValueByName("serialize") != false } 25 | private val deserializeFields get() = fields.filter { it.getValueByName("deserialize") != false } 26 | private val copyWithFields get() = fields.filter { it.getValueByName("copyWith") != false } 27 | 28 | fun addFiled( 29 | typeNodeInfo: FieldClassTypeInfo, 30 | name: String, 31 | isLate: Boolean, 32 | annotationValue: List? 33 | ) { 34 | //如果是?结尾是可空类型 35 | fields.add( 36 | Filed( 37 | typeNodeInfo, 38 | name, 39 | isLate, 40 | ).apply { 41 | this.annotationValue = annotationValue 42 | }) 43 | // fields.forEach { 44 | // it.genFromJson() 45 | // } 46 | } 47 | 48 | 49 | override fun toString(): String { 50 | val sb = StringBuffer() 51 | sb.append(jsonParseFunc()) 52 | sb.append("\n") 53 | sb.append("\n") 54 | sb.append(jsonGenFunc()) 55 | sb.append(copyWithFunc()) 56 | return sb.toString() 57 | } 58 | 59 | //生成fromjson方法 60 | private fun jsonParseFunc(): String { 61 | val sb = StringBuffer(); 62 | sb.append("\n") 63 | sb.append("$className \$${className}FromJson(Map json) {\n") 64 | val classInstanceName = className.toLowerCaseFirstOne() 65 | sb.append("\tfinal $className $classInstanceName = ${className}();\n") 66 | deserializeFields.forEach { k -> 67 | sb.append(k.generateFromJsonField(classInstanceName)) 68 | } 69 | sb.append("\treturn ${classInstanceName};\n") 70 | sb.append("}") 71 | return sb.toString() 72 | } 73 | 74 | 75 | //生成tojson方法 76 | private fun jsonGenFunc(): String { 77 | val sb = StringBuffer(); 78 | sb.append("Map \$${className}ToJson(${className} entity) {\n"); 79 | sb.append("\tfinal Map data = {};\n"); 80 | serializeFields.forEach { k -> 81 | sb.append("\t${k.toJsonExpression()}\n") 82 | } 83 | sb.append("\treturn data;\n"); 84 | sb.append("}"); 85 | return sb.toString() 86 | } 87 | 88 | 89 | private fun copyWithFunc(): String { 90 | val sb = StringBuffer() 91 | sb.append("\n") 92 | sb.append("\n") 93 | sb.append("extension ${className}Extension on $className {") 94 | sb.append("\n") 95 | if (copyWithFields.isNotEmpty()) { 96 | sb.append("\t$className copyWith({") 97 | sb.append("\n") 98 | copyWithFields.forEach { 99 | if (it.typeNodeInfo.primaryType == "dynamic") { 100 | sb.append("\t${it.typeNodeInfo.primaryType + (it.typeNodeInfo.genericityString ?: "")} ${it.name},\n") 101 | } else { 102 | sb.append("\t${it.typeNodeInfo.primaryType + (it.typeNodeInfo.genericityString ?: "")}? ${it.name},\n") 103 | } 104 | } 105 | sb.append("\t}) {\n") 106 | sb.append("\t\treturn $className()") 107 | copyWithFields.forEach { 108 | sb.append("\n\t\t\t..${it.name} = ${it.name} ?? this.${it.name}") 109 | } 110 | sb.append(";") 111 | sb.append("}") 112 | } 113 | sb.append("}") 114 | return sb.toString() 115 | 116 | } 117 | 118 | 119 | } 120 | 121 | class Filed( 122 | var typeNodeInfo: FieldClassTypeInfo, 123 | //字段名字 124 | var name: String, 125 | //是否是late修饰 126 | var isLate: Boolean, 127 | ) { 128 | 129 | //待定 130 | var isPrivate: Boolean? = null 131 | 132 | //注解的值 133 | var annotationValue: List? = null 134 | 135 | fun getValueByName(name: String): T? { 136 | return annotationValue?.firstOrNull { it.name == name }?.getValueByName() 137 | } 138 | 139 | /** 140 | * 生成formJson 字段 141 | */ 142 | fun generateFromJsonField(classInstanceName: String): String { 143 | val sb = StringBuffer() 144 | //class里的字段名 145 | //从json里取值的名称 146 | val jsonName = getValueByName("name") ?: name 147 | var jsonVal = "json['${jsonName}']" 148 | if (jsonName.contains('.')) { 149 | val split = jsonName.split(".") 150 | if (split.size > 1) { 151 | val result = StringBuilder() 152 | split.forEachIndexed { index, element -> 153 | val isLast = index == split.size - 1 154 | if (isLast) { 155 | result.append("?.getOrNull('$element')") 156 | } else { 157 | if (index > 0) { 158 | result.append("?") 159 | } 160 | result.append(".getOrNull>('$element')") 161 | } 162 | 163 | } 164 | jsonVal += " ?? json$result" 165 | } 166 | 167 | } 168 | ///如果是dynamic那么不写? 169 | val typeNullString = if (typeNodeInfo.primaryType == "dynamic" || typeNodeInfo.primaryType == "var") "" else "?" 170 | val a = "final ${typeNodeInfo.primaryType + (typeNodeInfo.genericityString ?: "")}${typeNullString} $name = ${ 171 | generateFromJsonByType( 172 | jsonVal, 173 | true, 174 | typeNodeInfo 175 | ) 176 | };" 177 | println("打印\n") 178 | println(a) 179 | sb.append(a) 180 | sb.append("\tif (${name} != null) {\n") 181 | sb.append("\t\t${classInstanceName}.$name = $name;") 182 | sb.append("\n") 183 | sb.append("\t}") 184 | return sb.toString() 185 | } 186 | 187 | /** 188 | * isRoot 如果是最外层的话,那么不写as 189 | */ 190 | private fun generateFromJsonByType(value: String, isRoot: Boolean, typeNodeInfo: FieldClassTypeInfo?): String { 191 | 192 | println("genFromType\n $value") 193 | println("genFromType\n $typeNodeInfo") 194 | return if (typeNodeInfo?.isMap() == true) { 195 | val a = generateFromJsonMap(value, typeNodeInfo) 196 | println("打印\n") 197 | println(a) 198 | a 199 | } else if (typeNodeInfo?.isList() == true || typeNodeInfo?.isSet() == true) { 200 | generateFromJsonList(value, isRoot, typeNodeInfo) 201 | } else { 202 | if (typeNodeInfo?.primaryType == null || typeNodeInfo.primaryType == "dynamic" || typeNodeInfo.primaryType == "var") { 203 | value 204 | } else { 205 | val sb = StringBuffer() 206 | 207 | sb.append("jsonConvert.convert<${typeNodeInfo.primaryType}>(${value}") 208 | ///是否是枚举 209 | val isEnum = getValueByName("isEnum") == true 210 | if (isEnum) { 211 | sb.append(", enumConvert: (v) => ${typeNodeInfo.primaryType}.values.byName(v)") 212 | } 213 | sb.append(")") 214 | if (!typeNodeInfo.nullable && !isRoot) { 215 | sb.append( 216 | " as ${typeNodeInfo.primaryType}" 217 | ) 218 | } 219 | sb.toString() 220 | } 221 | } 222 | } 223 | 224 | fun generateFromJsonMap(value: String, typeNodeInfo: FieldClassTypeInfo?): String { 225 | val nullString = nullString(typeNodeInfo?.nullable == true) 226 | val sb = StringBuffer() 227 | sb.append("\n") 228 | sb.append("\t\t") 229 | sb.append("(${value} as Map${nullString})${nullString}.map(") 230 | sb.append("\n") 231 | sb.append("\t") 232 | sb.append("(k, e) => MapEntry(k,") 233 | ///泛型带问号的时候 234 | if (typeNodeInfo?.genericityChildType?.genericityChildType?.nullable == true) { 235 | sb.append("\te == null ? null : ") 236 | } 237 | val genericityChildType = typeNodeInfo?.genericityChildType?.genericityChildType 238 | sb.append( 239 | generateFromJsonByType( 240 | "e", 241 | false, 242 | genericityChildType 243 | ) 244 | ) 245 | ///上面generateFromJsonByType已经添加了 那么这里就需要写了 246 | // ///并且不是list,如果是list的话那么就会有警告,因为不用转list了 247 | // if (genericityChildType?.nullable != true && genericityChildType?.isList() != true) { 248 | // sb.append( 249 | // " as ${genericityChildType?.primaryType}${ 250 | // nullString( 251 | // genericityChildType?.nullable 252 | // ) 253 | // }" 254 | // ) 255 | // } 256 | //MapEntry的括号 257 | sb.append(")") 258 | //map的括号 259 | sb.append(")") 260 | return sb.toString() 261 | } 262 | 263 | private fun generateFromJsonList(value: String, isRoot: Boolean, typeNodeInfo: FieldClassTypeInfo?): String { 264 | val sb = StringBuffer() 265 | ///如果是root,那么写成可null 266 | val nullString = nullString(typeNodeInfo?.nullable == true || isRoot) 267 | sb.append("(${value} as ${typeNodeInfo?.primaryType}${nullString})${nullString}.map(") 268 | sb.append("\n") 269 | sb.append("\t") 270 | val genericityChildType = typeNodeInfo?.genericityChildType 271 | sb.append("(e) => ${generateFromJsonByType("e", false, genericityChildType)}") 272 | ///上面generateFromJsonByType已经添加了 那么这里就需要写了 273 | // if (genericityChildType?.nullable != true) { 274 | // ///并且不是map,list,如果是的话那么就会有警告,因为不用转了 275 | // if (genericityChildType?.isMap() != true && genericityChildType?.isList() != true) { 276 | // sb.append( 277 | // " as ${genericityChildType?.primaryType}${genericityChildType?.genericityString ?: ""}${ 278 | // nullString( 279 | // genericityChildType?.nullable 280 | // ) 281 | // }" 282 | // ) 283 | // } 284 | // 285 | // } 286 | sb.append(")") 287 | sb.append(".to${typeNodeInfo?.primaryType}()") 288 | return sb.toString() 289 | } 290 | 291 | /** 292 | * 是否为null的字符串 293 | */ 294 | private fun nullString(nullable: Boolean?): String { 295 | return if (nullable == true) { 296 | "?" 297 | } else { 298 | "" 299 | } 300 | } 301 | 302 | 303 | fun toJsonExpression(): String { 304 | val type = typeNodeInfo.primaryType + (typeNodeInfo.genericityString ?: "") 305 | val name = name 306 | //从json里取值的名称 307 | val getJsonName = getValueByName("name") ?: name 308 | var getJsonKey = "data['${getJsonName}']" 309 | ///这里tojson无法判断原值是map还是带点的字符串 310 | // if (getJsonName.contains('.')) { 311 | // val split = getJsonName.split("."); 312 | // var temp = "data"; 313 | // for (i in 0 until split.size - 1) { 314 | // temp += ".putIfAbsent('${split[i]}', () => {})" 315 | // } 316 | // getJsonKey = temp + "['${split[split.size - 1]}']" 317 | // } 318 | val thisKey = "entity.$name" 319 | val isEnum = getValueByName("isEnum") == true 320 | when { 321 | typeNodeInfo.isList() || typeNodeInfo.isSet() -> { 322 | //1判断是否是基础数据类型 323 | //1.1拿到List的泛型 324 | val listSubType = typeNodeInfo.genericityChildType?.primaryType ?: "dynamic" 325 | //1.2判断是否是基础数据类型 326 | val value = if (isEnum) { 327 | "$thisKey${nullString(typeNodeInfo.nullable)}.map((v) => v${nullString(typeNodeInfo.genericityChildType?.nullable)}.name).to${typeNodeInfo.primaryType}()" 328 | } else if (isBaseType(listSubType)) { 329 | if (listSubType == "DateTime") { 330 | "$thisKey${nullString(typeNodeInfo.nullable)}.map((v) => v${nullString(typeNodeInfo.genericityChildType?.nullable)}.toIso8601String()).to${typeNodeInfo.primaryType}()" 331 | } else { 332 | thisKey 333 | } 334 | 335 | } else { 336 | //类名 337 | "$thisKey${nullString(typeNodeInfo.nullable)}.map((v) => v${nullString(typeNodeInfo.genericityChildType?.nullable)}.toJson()).to${typeNodeInfo.primaryType}()" 338 | } 339 | 340 | // class list 341 | return "$getJsonKey = $value;" 342 | } 343 | //是否是枚举 344 | isEnum -> { 345 | return "$getJsonKey = $thisKey${nullString(typeNodeInfo.nullable)}.name;" 346 | } 347 | //是否是基础数据类型 348 | isBaseType(type) -> { 349 | return when (type) { 350 | "DateTime" -> { 351 | "$getJsonKey = ${thisKey}${nullString(typeNodeInfo.nullable)}.toIso8601String();" 352 | } 353 | 354 | else -> "$getJsonKey = $thisKey;" 355 | } 356 | } 357 | //是map或者set 358 | typeNodeInfo.isMap() || typeNodeInfo.isSet() -> { 359 | return "$getJsonKey = $thisKey;" 360 | } 361 | // class 362 | else -> { 363 | return "$getJsonKey = ${thisKey}${nullString(typeNodeInfo.nullable)}.toJson();" 364 | } 365 | } 366 | } 367 | 368 | } 369 | 370 | @Suppress("UNCHECKED_CAST") 371 | class AnnotationValue(val name: String, private val value: Any) { 372 | fun getValueByName(): T { 373 | return value as T 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/action/jsontodart/ClassDefinition.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.action.jsontodart 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.github.zhangruiyu.flutterjsonbeanfactory.action.jsontodart.utils.* 5 | import com.github.zhangruiyu.flutterjsonbeanfactory.setting.Settings 6 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.FieldUtils 7 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.toLowerCaseFirstOne 8 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.toUpperCaseFirstOne 9 | 10 | class ClassDefinition(private val name: String, private val privateFields: Boolean = false) { 11 | ///map的key是json的key,没有转成dart的命名方式 12 | val fields = mutableMapOf() 13 | val dependencies: List 14 | get() { 15 | val dependenciesList = mutableListOf() 16 | fields.forEach { 17 | ///如不不是主类型 18 | if (it.value.isPrimitive.not()) { 19 | dependenciesList.add(Dependency(it.key, it.value)) 20 | } 21 | } 22 | return dependenciesList; 23 | } 24 | 25 | fun addField(key: String, typeDef: TypeDefinition) { 26 | fields[key] = typeDef 27 | } 28 | 29 | fun hasField(otherField: TypeDefinition): Boolean { 30 | return fields.keys.firstOrNull { k -> fields[k] == otherField } != null 31 | } 32 | 33 | override operator fun equals(other: Any?): Boolean { 34 | if (other is ClassDefinition) { 35 | if (name != other.name) { 36 | return false; 37 | } 38 | return fields.keys.firstOrNull { k -> 39 | other.fields.keys.firstOrNull { ok -> 40 | fields[k] == other.fields[ok] 41 | } == null 42 | } == null 43 | } 44 | return false 45 | } 46 | 47 | private fun _addTypeDef(typeDef: TypeDefinition, sb: StringBuffer, prefix: String, suffix: String) { 48 | if (typeDef.name == "Null") { 49 | sb.append("dynamic") 50 | } else { 51 | sb.append(prefix) 52 | sb.append(typeDef.name) 53 | if (typeDef.subtype != null) { 54 | //如果是list,就把名字修改成单数 55 | sb.append("<${typeDef.subtype!!}>") 56 | } 57 | sb.append(suffix) 58 | } 59 | } 60 | 61 | private fun _addCopyWithTypeDef(typeDef: TypeDefinition, sb: StringBuffer, suffix: String) { 62 | if (typeDef.name == "Null") { 63 | sb.append("dynamic") 64 | } else { 65 | sb.append(typeDef.name) 66 | if (typeDef.subtype != null) { 67 | //如果是list,就把名字修改成单数 68 | sb.append("<${typeDef.subtype!!}>") 69 | } 70 | sb.append(suffix) 71 | } 72 | } 73 | 74 | //字段的集合 75 | private val _fieldList: String 76 | get() { 77 | val settings = ApplicationManager.getApplication().getService(Settings::class.java) 78 | val isOpenNullAble = settings.isOpenNullAble == true 79 | val setDefault = settings.setDefault == true 80 | 81 | val suffix = if (isOpenNullAble) "?" else "" 82 | return fields.keys.map { key -> 83 | val f = fields[key]!! 84 | ///如果不是 主类型或着List类型,那就当类判断 85 | val isClass = (f.isPrimitive || isListType(f.name)).not() 86 | ///如果是类 87 | val prefix = if (isClass) { 88 | ///如果没开可空 89 | if (!isOpenNullAble) { 90 | "late " 91 | } else { 92 | "" 93 | } 94 | } else { 95 | ///这里是正常字段 96 | ///没有开启可空,没有设置默认值,并且不是Primitive 97 | if ((!isOpenNullAble && !setDefault)) "late " else "" 98 | } 99 | 100 | ///给key转成dart写法 101 | val fieldName = FieldUtils.toFieldTypeName(key).toLowerCaseFirstOne() 102 | val sb = StringBuffer(); 103 | //如果驼峰命名后不一致,才这样 104 | if (fieldName != key) { 105 | sb.append('\t') 106 | sb.append("@JSONField(name: \'${key}\')\n") 107 | } 108 | sb.append('\t') 109 | _addTypeDef(f, sb, prefix, suffix) 110 | sb.append(" $fieldName") 111 | if (settings.setDefault == true) { 112 | if (isListType(f.name)) { 113 | if (settings.listFieldDefaultValue() 114 | ?.isNotEmpty() == true 115 | ) { 116 | sb.append(" = ${settings.listFieldDefaultValue()}") 117 | } 118 | } else if (f.subtype == null) { 119 | if (f.name == "String" && settings.stringFieldDefaultValue()?.isNotEmpty() == true) { 120 | sb.append(" = ${settings.stringFieldDefaultValue()}") 121 | } else if (f.name == "bool" && settings.stringFieldDefaultValue()?.isNotEmpty() == true) { 122 | sb.append(" = ${settings.boolFieldDefaultValue()}") 123 | } else if (f.name == "int" && settings.stringFieldDefaultValue()?.isNotEmpty() == true) { 124 | sb.append(" = ${settings.intFieldDefaultValue()}") 125 | } 126 | } 127 | } 128 | sb.append(";") 129 | return@map sb.toString() 130 | }.joinToString("\n") 131 | } 132 | 133 | 134 | override fun toString(): String { 135 | return if (privateFields) { 136 | // "class $name {\n$_fieldList\n\n$_defaultPrivateConstructor\n\n$_gettersSetters\n\n$_jsonParseFunc\n\n$_jsonGenFunc\n}\n"; 137 | "" 138 | } else { 139 | val sb = StringBuffer() 140 | sb.append("@JsonSerializable()") 141 | sb.append("\n") 142 | sb.append("class $name {") 143 | sb.append("\n") 144 | sb.append(_fieldList) 145 | sb.append("\n\n") 146 | sb.append("\t${name}();") 147 | sb.append("\n\n") 148 | sb.append("\tfactory ${name}.fromJson(Map json) => \$${name}FromJson(json);") 149 | sb.append("\n\n") 150 | sb.append("\tMap toJson() => \$${name}ToJson(this);") 151 | sb.append("\n") 152 | sb.append("\n") 153 | sb.append("\t@override") 154 | sb.append("\n") 155 | sb.append("\tString toString() {") 156 | sb.append("\n") 157 | sb.append("\t\treturn jsonEncode(this);") 158 | sb.append("\n") 159 | sb.append("\t}") 160 | sb.append("\n") 161 | sb.append("}") 162 | sb.toString() 163 | } 164 | } 165 | } 166 | 167 | 168 | class Dependency(var name: String, var typeDef: TypeDefinition) { 169 | val className: String 170 | get() { 171 | return FieldUtils.toFieldTypeName(name) 172 | } 173 | 174 | override fun toString(): String { 175 | return "name = ${name} ,typeDef = ${typeDef}" 176 | } 177 | } 178 | 179 | class TypeDefinition(var name: String, var subtype: String? = null) { 180 | 181 | 182 | val isPrimitive: Boolean = if (subtype == null) { 183 | isPrimitiveType(name) 184 | } else { 185 | isPrimitiveType("$name<${subtype!!.toUpperCaseFirstOne()}>") 186 | } 187 | private val isPrimitiveList: Boolean = isPrimitive && name == "List" 188 | 189 | companion object { 190 | fun fromDynamic(obj: Any?): TypeDefinition { 191 | val type = getTypeName(obj) 192 | if (type == "List") { 193 | val list = obj as List<*> 194 | val firstElementType = if (list.isNotEmpty()) { 195 | getTypeName(list[0]) 196 | } else { 197 | // when array is empty insert Null just to warn the user 198 | "dynamic" 199 | } 200 | return TypeDefinition(type, firstElementType) 201 | } 202 | return TypeDefinition(type) 203 | } 204 | } 205 | 206 | 207 | override operator fun equals(other: Any?): Boolean { 208 | if (other is TypeDefinition) { 209 | return name == other.name && subtype == other.subtype; 210 | } 211 | return false; 212 | } 213 | 214 | 215 | override fun hashCode(): Int { 216 | var result = name.hashCode() 217 | result = 31 * result + (subtype?.hashCode() ?: 0) 218 | result = 31 * result + isPrimitive.hashCode() 219 | result = 31 * result + isPrimitiveList.hashCode() 220 | return result 221 | } 222 | 223 | override fun toString(): String { 224 | return "TypeDefinition(name='$name', subtype=$subtype, isPrimitive=$isPrimitive, isPrimitiveList=$isPrimitiveList)" 225 | } 226 | 227 | 228 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/action/jsontodart/CollectInfo.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.action.jsontodart 2 | 3 | import com.intellij.openapi.components.ServiceManager 4 | import com.github.zhangruiyu.flutterjsonbeanfactory.setting.Settings 5 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.toLowerCaseFirstOne 6 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.toUpperCaseFirstOne 7 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.upperCharToUnderLine 8 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.upperTable 9 | 10 | class CollectInfo { 11 | //用户输入的类名 12 | var userInputClassName = "" 13 | var userInputJson = "" 14 | //用户设置的后缀 15 | fun modelSuffix(): String { 16 | return ServiceManager.getService(Settings::class.java).state.modelSuffix.toLowerCase() 17 | } 18 | 19 | //用户输入的类名转为文件名 20 | fun transformInputClassNameToFileName(): String { 21 | return if (!userInputClassName.contains("_")) { 22 | (userInputClassName + modelSuffix().toUpperCaseFirstOne()).upperCharToUnderLine() 23 | } else { 24 | (userInputClassName + "_" + modelSuffix().toLowerCaseFirstOne()) 25 | } 26 | 27 | } 28 | 29 | 30 | //用户输入的名字转为首个class的名字(文件中的类名) 31 | fun firstClassName(): String { 32 | return if (userInputClassName.contains("_")) { 33 | (upperTable(userInputClassName)).toUpperCaseFirstOne() 34 | } else { 35 | (userInputClassName).toUpperCaseFirstOne() 36 | } 37 | } 38 | 39 | //用户输入的名字转为首个class的名字(文件中的类名) 40 | fun firstClassEntityName(): String { 41 | return if (userInputClassName.contains("_")) { 42 | (upperTable(userInputClassName).toUpperCaseFirstOne() + modelSuffix().toUpperCaseFirstOne()) 43 | } else { 44 | (userInputClassName.toUpperCaseFirstOne() + modelSuffix().toUpperCaseFirstOne()) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/action/jsontodart/JsonToDartBeanAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.action.jsontodart 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.actionSystem.LangDataKeys 6 | import com.intellij.openapi.actionSystem.PlatformDataKeys 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.openapi.roots.ModuleRootManager 9 | import com.intellij.psi.PsiDirectory 10 | import com.intellij.psi.PsiFile 11 | import com.intellij.psi.PsiFileFactory 12 | import com.intellij.psi.PsiManager 13 | import com.jetbrains.lang.dart.DartFileType 14 | import com.jetbrains.lang.dart.psi.DartFile 15 | import com.github.zhangruiyu.flutterjsonbeanfactory.action.dart_to_helper.FlutterBeanFactoryAction 16 | import com.github.zhangruiyu.flutterjsonbeanfactory.file.FileHelpers 17 | import com.github.zhangruiyu.flutterjsonbeanfactory.ui.JsonInputDialog 18 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.* 19 | 20 | class JsonToDartBeanAction : AnAction("JsonToDartBeanAction") { 21 | 22 | override fun actionPerformed(event: AnActionEvent) { 23 | val project = event.getData(PlatformDataKeys.PROJECT) ?: return 24 | val pubSpecConfig = YamlHelper.getPubSpecConfig(project) 25 | val dataContext = event.dataContext 26 | val module = LangDataKeys.MODULE.getData(dataContext) ?: return 27 | 28 | val navigatable = LangDataKeys.NAVIGATABLE.getData(dataContext) 29 | val directory = when (navigatable) { 30 | is PsiDirectory -> navigatable 31 | is PsiFile -> navigatable.containingDirectory 32 | else -> { 33 | val root = ModuleRootManager.getInstance(module) 34 | root.sourceRoots 35 | .asSequence() 36 | .mapNotNull { 37 | PsiManager.getInstance(project).findDirectory(it) 38 | }.firstOrNull() 39 | } 40 | } ?: return 41 | // val directoryFactory = PsiDirectoryFactory.getInstance(directory.project) 42 | // val packageName = directoryFactory.getQualifiedName(directory, true) 43 | val psiFileFactory = PsiFileFactory.getInstance(project) 44 | 45 | try { 46 | JsonInputDialog(project) { collectInfo -> 47 | //文件名字 48 | //如果包含那么就提示 49 | val fileName = collectInfo.transformInputClassNameToFileName() 50 | when { 51 | FileHelpers.containsDirectoryFile(directory, "$fileName.dart") -> { 52 | project.showErrorMessage("The $fileName.dart already exists") 53 | false 54 | } 55 | 56 | FileHelpers.containsProjectFile(project, "$fileName.dart") -> { 57 | project.showErrorMessage("$fileName.dart already exists in other package") 58 | false 59 | } 60 | 61 | else -> { 62 | //生成dart文件的内容 63 | val generatorClassContent = 64 | ModelGenerator(collectInfo, project).generateDartClassesToString( 65 | fileName, 66 | pubSpecConfig?.generatedPath ?: GENERATED_PATH_DEFAULT 67 | ) 68 | generateDartDataClassFile( 69 | fileName, 70 | generatorClassContent, 71 | project, 72 | psiFileFactory, 73 | directory 74 | ) 75 | val notifyMessage = "Dart Data Class file generated successful" 76 | FlutterBeanFactoryAction.generateAllFile(project) 77 | project.showNotify(notifyMessage) 78 | true 79 | } 80 | } 81 | 82 | }.show() 83 | } catch (e: Exception) { 84 | project.showNotify(e.message!!) 85 | } 86 | 87 | } 88 | 89 | 90 | private fun generateDartDataClassFile( 91 | fileName: String, 92 | classCodeContent: String, 93 | project: Project?, 94 | psiFileFactory: PsiFileFactory, 95 | directory: PsiDirectory 96 | ) { 97 | 98 | project.executeCouldRollBackAction { 99 | 100 | val file = 101 | psiFileFactory.createFileFromText("$fileName.dart", DartFileType.INSTANCE, classCodeContent) as DartFile 102 | directory.add(file) 103 | //包名 104 | // val packageName = (directory.virtualFile.path + "/$fileName.dart").substringAfter("${project!!.name}/lib/") 105 | // 生成单个helper 106 | // FileHelpers.generateDartEntityHelper(project, "import 'package:${project.name}/${packageName}';", FileHelpers.getDartFileHelperClassGeneratorInfo(file)) 107 | //此时应该重新生成所有文件 108 | 109 | } 110 | } 111 | 112 | /* private fun changeDartFileNameIfCurrentDirectoryExistTheSameFileNameWithoutSuffix( 113 | fileName: String, 114 | directory: PsiDirectory 115 | ): String { 116 | var newFileName = fileName 117 | val dartFileSuffix = ".dart" 118 | val fileNamesWithoutSuffix = 119 | directory.files.filter { it.name.endsWith(dartFileSuffix) } 120 | .map { it.name.dropLast(dartFileSuffix.length) } 121 | while (fileNamesWithoutSuffix.contains(newFileName)) { 122 | newFileName += "X" 123 | } 124 | return newFileName 125 | }*/ 126 | 127 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/action/jsontodart/ModelGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.action.jsontodart 2 | 3 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.* 4 | import com.google.gson.GsonBuilder 5 | import com.google.gson.reflect.TypeToken 6 | import com.intellij.openapi.project.Project 7 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.GsonUtil.MapTypeAdapter 8 | 9 | 10 | class ModelGenerator( 11 | val collectInfo: CollectInfo, 12 | val project: Project 13 | ) { 14 | var isFirstClass = true 15 | var allClasses = mutableListOf() 16 | 17 | //parentType 父类型 是list 或者class 18 | private fun generateClassDefinition( 19 | className: String, 20 | parentName: String, 21 | jsonRawData: Any, 22 | parentType: String = "" 23 | ): MutableList { 24 | val newClassName = parentName + className 25 | val preName = newClassName 26 | if (jsonRawData is List<*>) { 27 | // if first element is an array, start in the first element. 28 | generateClassDefinition(newClassName, newClassName, jsonRawData[0]!!) 29 | } else if (jsonRawData is Map<*, *>) { 30 | val keys = jsonRawData.keys.map { it.toString() } 31 | //如果是list,就把名字修改成单数 32 | val classDefinition = ClassDefinition( 33 | when { 34 | "list" == parentType -> { 35 | newClassName 36 | } 37 | 38 | isFirstClass -> {//如果是第一个类 39 | isFirstClass = false 40 | newClassName + collectInfo.modelSuffix().toUpperCaseFirstOne() 41 | } 42 | 43 | else -> { 44 | newClassName 45 | } 46 | } 47 | ) 48 | keys.forEach { key -> 49 | val typeDef = TypeDefinition.fromDynamic(jsonRawData[key]) 50 | val fieldTypeName = FieldUtils.toFieldTypeName(key) 51 | if (typeDef.name == "Class") { 52 | typeDef.name = preName + fieldTypeName 53 | } 54 | if (typeDef.subtype != null && typeDef.subtype == "Class") { 55 | typeDef.subtype = preName + fieldTypeName 56 | } 57 | classDefinition.addField(key, typeDef) 58 | } 59 | if (allClasses.firstOrNull { cd -> cd == classDefinition } == null) { 60 | allClasses.add(classDefinition) 61 | } 62 | val dependencies = classDefinition.dependencies 63 | dependencies.forEach { dependency -> 64 | if (dependency.typeDef.name == "List") { 65 | if (((jsonRawData[dependency.name]) as? List<*>)?.isNotEmpty() == true) { 66 | val names = (jsonRawData[dependency.name] as List<*>) 67 | generateClassDefinition(dependency.className, newClassName, names[0]!!, "list") 68 | } 69 | } else { 70 | generateClassDefinition(dependency.className, newClassName, jsonRawData[dependency.name]!!) 71 | } 72 | } 73 | } 74 | return allClasses 75 | } 76 | 77 | fun generateDartClassesToString(fileName: String, generatedPath: String): String { 78 | //用阿里的防止int变为double 已解决 还是用google的吧 https://www.codercto.com/a/73857.html 79 | // val jsonRawData = JSON.parseObject(collectInfo.userInputJson) 80 | val originalStr = collectInfo.userInputJson.trim() 81 | val gson = GsonBuilder() 82 | .registerTypeAdapter(object : TypeToken>() {}.type, MapTypeAdapter()).create() 83 | 84 | val jsonRawData = if (originalStr.startsWith("[")) { 85 | val list: List = gson.fromJson(originalStr, object : TypeToken>() {}.type) 86 | try { 87 | (JsonUtils.jsonMapMCompletion(list) as List<*>).first() 88 | } catch (e: Exception) { 89 | mutableMapOf() 90 | } 91 | 92 | } else { 93 | gson.fromJson>(originalStr, object : TypeToken>() {}.type) 94 | } 95 | // val jsonRawData = gson.fromJson>(collectInfo.userInputJson, HashMap::class.java) 96 | val pubSpecConfig = YamlHelper.getPubSpecConfig(project) 97 | val classContentList = generateClassDefinition( 98 | collectInfo.firstClassName(), "", JsonUtils.jsonMapMCompletion(jsonRawData) 99 | ?: mutableMapOf() 100 | ) 101 | val classContent = classContentList.joinToString("\n\n") 102 | classContentList.fold(mutableListOf()) { acc, de -> 103 | acc.addAll(de.fields.map { it.value }) 104 | acc 105 | } 106 | val stringBuilder = StringBuilder() 107 | //导包 108 | stringBuilder.append("import 'package:${pubSpecConfig?.name}/${generatedPath}/base/json_field.dart';") 109 | stringBuilder.append("\n") 110 | stringBuilder.append("import 'package:${pubSpecConfig?.name}/${generatedPath}/${fileName}.g.dart';") 111 | stringBuilder.append("\n") 112 | stringBuilder.append("import 'dart:convert';") 113 | stringBuilder.append("\n") 114 | ///这个是方便copyWith调用 115 | stringBuilder.append("export 'package:${pubSpecConfig?.name}/${generatedPath}/${fileName}.g.dart';") 116 | stringBuilder.append("\n") 117 | stringBuilder.append("\n") 118 | stringBuilder.append(classContent) 119 | //生成helper类 120 | 121 | //生成 122 | return stringBuilder.toString() 123 | } 124 | 125 | 126 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/action/jsontodart/utils/Helper.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.action.jsontodart.utils 2 | 3 | import com.github.zhangruiyu.flutterjsonbeanfactory.action.jsontodart.TypeDefinition 4 | //import com.github.zhangruiyu.flutterjsonbeanfactory.utils.DateUtil 5 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.LogUtil 6 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.toUpperCaseFirstOne 7 | import io.flutter.FlutterUtils 8 | import java.math.BigDecimal 9 | 10 | private val PRIMITIVE_TYPES = mapOf( 11 | "int" to true, 12 | "num" to true, 13 | "double" to true, 14 | "String" to true, 15 | "bool" to true, 16 | "List" to true, 17 | "DateTime" to true, 18 | "List" to true, 19 | "List" to true, 20 | "List" to true, 21 | "List" to true, 22 | "List" to true, 23 | "List" to true, 24 | "List" to true, 25 | "List" to true, 26 | "Null" to true, 27 | "var" to true, 28 | "dynamic" to true 29 | ) 30 | 31 | //基础类型 32 | private val BASE_TYPES = mapOf( 33 | "int" to true, 34 | "num" to true, 35 | "double" to true, 36 | "String" to true, 37 | "bool" to true, 38 | "List" to true, 39 | "DateTime" to true, 40 | "Null" to true, 41 | "var" to true, 42 | "dynamic" to true 43 | ) 44 | 45 | /** 46 | * 是否是主数据类型 47 | */ 48 | fun isPrimitiveType(typeName: String): Boolean { 49 | return PRIMITIVE_TYPES[typeName.replace("?", "")] ?: false 50 | } 51 | 52 | /** 53 | * 是否是基础数据类型 54 | */ 55 | fun isBaseType(typeName: String): Boolean { 56 | return BASE_TYPES[typeName] ?: false 57 | } 58 | 59 | 60 | //是否是List类型 61 | fun isListType(typeName: String): Boolean { 62 | return when { 63 | typeName.contains("List<") -> { 64 | true 65 | } 66 | else -> { 67 | typeName == "List" 68 | } 69 | } 70 | } 71 | 72 | fun getTypeName(obj: Any?): String { 73 | return when (obj) { 74 | is String -> /*if (DateUtil.canParseDate(obj.toString())) "DateTime" else*/ "String" 75 | is Int -> "int" 76 | is Double -> "double" 77 | is Long -> "int" 78 | is BigDecimal -> "double" 79 | is Boolean -> "bool" 80 | null -> "Null" 81 | is List<*> -> "List" 82 | else -> // assumed class 83 | "Class" 84 | } 85 | } 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/file/FileHelpers.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.file 2 | 3 | //import org.jetbrains.kotlin.idea.core.util.toPsiFile 4 | //import org.jetbrains.kotlin.idea.refactoring.toPsiFile 5 | 6 | import com.github.zhangruiyu.flutterjsonbeanfactory.action.dart_to_helper.node.GeneratorDartClassNodeToHelperInfo 7 | import com.github.zhangruiyu.flutterjsonbeanfactory.action.dart_to_helper.node.HelperFileGeneratorInfo 8 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.* 9 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.commitContent 10 | import com.intellij.openapi.application.ApplicationManager 11 | import com.intellij.openapi.command.CommandProcessor 12 | import com.intellij.openapi.command.WriteCommandAction 13 | import com.intellij.openapi.project.Project 14 | import com.intellij.openapi.project.guessProjectDir 15 | import com.intellij.openapi.vfs.VirtualFile 16 | import com.intellij.psi.PsiDirectory 17 | import com.intellij.psi.PsiManager 18 | import com.intellij.psi.search.FilenameIndex 19 | import io.flutter.pub.PubRoot 20 | import java.io.File 21 | 22 | 23 | //import io.flutter.utils.FlutterModuleUtils 24 | 25 | object FileHelpers { 26 | @JvmStatic 27 | fun getResourceFolder(project: Project): VirtualFile { 28 | val guessProjectDir = project.guessProjectDir() 29 | return guessProjectDir?.findChild("res") 30 | ?: guessProjectDir!!.createChildDirectory(this, "res") 31 | } 32 | 33 | @JvmStatic 34 | fun getValuesFolder(project: Project): VirtualFile { 35 | val resFolder = getResourceFolder(project) 36 | return resFolder.findChild("values") 37 | ?: resFolder.createChildDirectory(this, "values") 38 | } 39 | 40 | /** 41 | * 获取jsonfiled.dart 42 | */ 43 | fun getJsonConvertJsonFiledFile(project: Project, generatedPath: String, callback: (file: VirtualFile) -> Unit) { 44 | ApplicationManager.getApplication().runWriteAction { 45 | val generated = getJsonConvertBaseFile(project, generatedPath) 46 | callback(generated.findOrCreateChildData(this, "json_field.dart")) 47 | } 48 | } 49 | 50 | /** 51 | * 获取generated/json/base目录 52 | */ 53 | fun getJsonConvertBaseFile(project: Project, generatedPath: String): VirtualFile { 54 | return getGeneratedFile(project, generatedPath).let { json -> 55 | json.findChild("base") 56 | ?: json.createChildDirectory(this, "base") 57 | } 58 | } 59 | 60 | /** 61 | * 62 | */ 63 | private fun getEntityHelperFile( 64 | project: Project, 65 | fileName: String, 66 | generatedPath: String, 67 | callback: (file: VirtualFile) -> Unit 68 | ) { 69 | val generated = getGeneratedFile(project, generatedPath) 70 | callback(generated.findOrCreateChildData(this, fileName)) 71 | } 72 | 73 | 74 | /** 75 | * 获取generated/json自动生成目录 76 | */ 77 | fun getGeneratedFile(project: Project, generatedPath: String): VirtualFile { 78 | val libFile = PubRoot.forFile(getProjectIdeaFile(project))?.lib!! 79 | var targetFile: VirtualFile = libFile 80 | generatedPath.split("/").forEach { 81 | targetFile = (targetFile.findChild(it) 82 | ?: targetFile.createChildDirectory(targetFile, it)) 83 | } 84 | return targetFile 85 | } 86 | 87 | /** 88 | * 获取项目.idea目录的一个文件 89 | */ 90 | fun getProjectIdeaFile(project: Project): VirtualFile? { 91 | val ideaFile = project.projectFile ?: project.workspaceFile ?: project.guessProjectDir()?.children?.first() 92 | if (ideaFile == null) { 93 | project.showErrorMessage("Missing .idea/misc.xml or .idea/workspace.xml file") 94 | } 95 | return ideaFile 96 | } 97 | 98 | /** 99 | * 获取generated/json自动生成目录 100 | */ 101 | fun getGeneratedFileRun(project: Project, generatedPath: String, callback: (file: VirtualFile) -> Unit) { 102 | ApplicationManager.getApplication().runWriteAction { 103 | callback(getGeneratedFile(project, generatedPath)) 104 | } 105 | } 106 | 107 | 108 | /** 109 | * 自动生成所有文件的辅助文件 110 | */ 111 | fun generateAllDartEntityHelper( 112 | project: Project, 113 | generatedPath: String, 114 | allClass: List> 115 | ) { 116 | // 使用 WriteCommandAction 包装整个循环操作 117 | WriteCommandAction.runWriteCommandAction(project) { 118 | allClass.forEach { 119 | val packageName = it.second 120 | val helperClassGeneratorInfos = it.first 121 | val content = StringBuilder() 122 | //导包 123 | val pubSpecConfig = YamlHelper.getPubSpecConfig(project) 124 | //辅助主类的包名 125 | content.append("import 'package:${pubSpecConfig?.name}/${generatedPath}/base/json_convert_content.dart';\n") 126 | content.append(packageName) 127 | content.append("\n") 128 | //所有字段 129 | /* val allFields = helperClassGeneratorInfos?.classes?.flatMap { 130 | it.fields.mapNotNull { itemFiled -> 131 | itemFiled.annotationValue 132 | }.flatMap { annotationList -> 133 | annotationList.asIterable() 134 | } 135 | }*/ 136 | helperClassGeneratorInfos.imports.filterNot { 137 | it.endsWith("json_field.dart';") || it.contains("dart:convert") || it.endsWith( 138 | ".g.dart';" 139 | ) 140 | }.forEach { itemImport -> 141 | content.append(itemImport) 142 | content.append("\n\n") 143 | } 144 | content.append(helperClassGeneratorInfos.classes.joinToString("\n")) 145 | //创建文件 146 | getEntityHelperFile( 147 | project, 148 | "${File(packageName).nameWithoutExtension}.g.dart", 149 | generatedPath 150 | ) { file -> 151 | file.commitContent(project, content.toString()) 152 | } 153 | } 154 | } 155 | 156 | } 157 | 158 | /** 159 | * 获取所有符合生成的file 160 | */ 161 | fun getAllEntityFiles(project: Project): List> { 162 | val pubSpecConfig = YamlHelper.getPubSpecConfig(project) 163 | val psiManager = PsiManager.getInstance(project) 164 | return FilenameIndex.getAllFilesByExt(project, "dart").filter { 165 | //不过滤entity结尾了 166 | (it.path.contains("${project.name}/lib/") || it.path.contains("${pubSpecConfig?.name}/lib/")) && !it.path.contains("freezed") 167 | }.sortedBy { 168 | println(it.path) 169 | it.path 170 | }.mapNotNull { 171 | try { 172 | val dartFileHelperClassGeneratorInfo = 173 | GeneratorDartClassNodeToHelperInfo.getDartFileHelperClassGeneratorInfo(psiManager.findFile(it)!!) 174 | //导包 175 | if (dartFileHelperClassGeneratorInfo == null) { 176 | null 177 | } else { 178 | //包名 179 | val packageName = (it.path).substringAfter("/lib/") 180 | dartFileHelperClassGeneratorInfo to "import 'package:${pubSpecConfig?.name}/${packageName}';" 181 | } 182 | } catch (e: Exception) { 183 | val errorString = "error file: ${it},stackTrace: ${e.stackTraceToString()}" 184 | println(errorString) 185 | project.showNotify(errorString) 186 | null 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * 判断项目中是否包含这个file 193 | */ 194 | fun containsProjectFile(project: Project, fileName: String): Boolean { 195 | return FilenameIndex.getAllFilesByExt(project, "dart").firstOrNull { 196 | File(it.path).name == fileName 197 | } != null 198 | } 199 | 200 | /** 201 | * 判断Directory中是否包含这个file 202 | */ 203 | fun containsDirectoryFile(directory: PsiDirectory, fileName: String): Boolean { 204 | return directory.files.filter { it.name.endsWith(".dart") } 205 | .firstOrNull { it.name == fileName } != null 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/setting/SettingComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.setting 2 | 3 | import com.intellij.openapi.components.ServiceManager 4 | import com.intellij.openapi.components.State 5 | import com.intellij.openapi.components.Storage 6 | import com.intellij.openapi.options.Configurable 7 | import javax.swing.JComponent 8 | 9 | @State(name = "FlutterJsonBeanSetting", storages = [Storage("FlutterJsonBeanSetting.xml")]) 10 | class SettingComponent : Configurable { 11 | private var settingLayout: SettingLayout? = null 12 | override fun isModified(): Boolean { 13 | if (settingLayout == null) { 14 | return false 15 | } 16 | return getSettings() != Settings( 17 | settingLayout!!.getModelSuffix(), isOpenNullAble = false, setDefault = false 18 | ) 19 | } 20 | 21 | override fun getDisplayName(): String { 22 | return "FlutterJsonBeanFactory" 23 | } 24 | 25 | override fun apply() { 26 | settingLayout?.run { 27 | getSettings().apply { 28 | modelSuffix = getModelSuffix() 29 | } 30 | } 31 | } 32 | 33 | 34 | override fun createComponent(): JComponent { 35 | settingLayout = SettingLayout(getSettings()) 36 | return settingLayout!!.getRootComponent() 37 | } 38 | 39 | private fun getSettings(): Settings { 40 | return ServiceManager.getService(Settings::class.java).state 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/setting/SettingLayout.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.setting 2 | 3 | import com.intellij.ui.components.JBLabel 4 | import com.intellij.ui.components.JBTextField 5 | import com.intellij.util.ui.JBDimension 6 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.addComponentIntoVerticalBoxAlignmentLeft 7 | import java.awt.BorderLayout 8 | import javax.swing.* 9 | import javax.swing.border.EmptyBorder 10 | 11 | 12 | class SettingLayout(settingState: Settings) { 13 | private val panel: JPanel = JPanel(BorderLayout()) 14 | private val beanNameTextField: JBTextField 15 | 16 | init { 17 | 18 | val beanNameLayout = createLinearLayoutVertical() 19 | val beanName = JBLabel() 20 | beanName.border = EmptyBorder(5, 0, 5, 0) 21 | beanName.text = "model suffix" 22 | beanNameLayout.addComponentIntoVerticalBoxAlignmentLeft(beanName) 23 | beanNameTextField = JBTextField(settingState.modelSuffix) 24 | beanNameTextField.preferredSize = JBDimension(400, 40) 25 | beanNameLayout.addComponentIntoVerticalBoxAlignmentLeft(beanNameTextField) 26 | 27 | 28 | panel.add(beanNameLayout, BorderLayout.NORTH) 29 | 30 | val label1 = JBLabel() 31 | label1.border = EmptyBorder(5, 0, 5, 0) 32 | label1.text = "Configure scan suffix files(Please separate them with commas)" 33 | beanNameLayout.addComponentIntoVerticalBoxAlignmentLeft( 34 | label1 35 | ) 36 | panel.add(createLinearLayoutVertical(), BorderLayout.AFTER_LAST_LINE) 37 | } 38 | 39 | fun getRootComponent(): JComponent { 40 | return this.panel 41 | } 42 | 43 | 44 | fun getModelSuffix(): String { 45 | return beanNameTextField.text 46 | } 47 | 48 | 49 | } 50 | 51 | fun createLinearLayoutVertical(): JPanel { 52 | val container = JPanel() 53 | val boxLayout = BoxLayout(container, BoxLayout.PAGE_AXIS) 54 | container.layout = boxLayout 55 | return container 56 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/setting/Settings.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.setting 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent 4 | import com.intellij.openapi.components.State 5 | import com.intellij.openapi.components.Storage 6 | import com.intellij.util.xmlb.XmlSerializerUtil 7 | 8 | @State(name = "FlutterJsonBeanFactorySettings", storages = [(Storage("FlutterJsonBeanFactorySettings.xml"))]) 9 | data class Settings( 10 | var modelSuffix: String, 11 | var isOpenNullAble: Boolean?, 12 | var setDefault: Boolean?, 13 | var boolDefaultValue: String = "false", 14 | var stringDefaultValue: String = "''", 15 | var intDefaultValue: String = "0", 16 | var listDefaultValue: String = "[]", 17 | ) : PersistentStateComponent { 18 | 19 | constructor() : this( 20 | "entity", null, null 21 | ) 22 | 23 | override fun getState(): Settings { 24 | return this 25 | } 26 | 27 | override fun loadState(state: Settings) { 28 | XmlSerializerUtil.copyBean(state, this) 29 | } 30 | 31 | fun stringFieldDefaultValue(): String? { 32 | return if (setDefault == true && stringDefaultValue.isNotEmpty()) { 33 | stringDefaultValue 34 | } else { 35 | null 36 | } 37 | } 38 | 39 | fun boolFieldDefaultValue(): String? { 40 | return if (setDefault == true && boolDefaultValue.isNotEmpty()) { 41 | boolDefaultValue 42 | } else { 43 | null 44 | } 45 | } 46 | 47 | fun intFieldDefaultValue(): String? { 48 | return if (setDefault == true && intDefaultValue.isNotEmpty()) { 49 | intDefaultValue 50 | } else { 51 | null 52 | } 53 | } 54 | 55 | fun listFieldDefaultValue(): String? { 56 | return if (setDefault == true && listDefaultValue.isNotEmpty()) { 57 | listDefaultValue 58 | } else { 59 | null 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/ui/JsonInputDialog.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.ui 2 | 3 | import com.google.gson.* 4 | import com.intellij.openapi.components.ServiceManager 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.openapi.ui.DialogPanel 7 | import com.intellij.openapi.ui.InputValidator 8 | import com.intellij.openapi.ui.Messages 9 | import com.intellij.ui.DocumentAdapter 10 | import com.intellij.ui.components.JBCheckBox 11 | import com.intellij.ui.components.JBLabel 12 | import com.intellij.ui.components.JBScrollPane 13 | import com.intellij.ui.layout.CellBuilder 14 | import com.intellij.ui.layout.panel 15 | import com.intellij.util.ui.JBDimension 16 | import com.intellij.util.ui.JBEmptyBorder 17 | import com.github.zhangruiyu.flutterjsonbeanfactory.action.jsontodart.CollectInfo 18 | import com.github.zhangruiyu.flutterjsonbeanfactory.setting.Settings 19 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.addComponentIntoVerticalBoxAlignmentLeft 20 | import com.intellij.openapi.application.ApplicationManager 21 | import com.intellij.util.ui.JBUI 22 | import java.awt.BorderLayout 23 | import java.awt.Component 24 | import java.awt.FlowLayout 25 | import java.awt.Insets 26 | import java.awt.event.ActionEvent 27 | import javax.swing.* 28 | import javax.swing.event.DocumentEvent 29 | import javax.swing.text.JTextComponent 30 | 31 | /** 32 | * Dialog widget relative 33 | * Created by Seal.wu on 2017/9/21. 34 | */ 35 | 36 | 37 | class MyInputValidator : InputValidator { 38 | 39 | lateinit var classNameField: JTextField 40 | override fun checkInput(inputString: String): Boolean { 41 | return try { 42 | val classNameLegal = classNameField.text.trim().isNotBlank() 43 | inputIsValidJson(inputString) && classNameLegal 44 | } catch (e: JsonSyntaxException) { 45 | false 46 | } 47 | 48 | } 49 | 50 | private fun inputIsValidJson(string: String) = try { 51 | val jsonElement = JsonParser().parse(string) 52 | (jsonElement.isJsonObject || jsonElement.isJsonArray) 53 | } catch (e: JsonSyntaxException) { 54 | false 55 | } 56 | 57 | override fun canClose(inputString: String): Boolean { 58 | return true 59 | } 60 | } 61 | 62 | val myInputValidator = MyInputValidator() 63 | 64 | /** 65 | * Json input Dialog 66 | */ 67 | open class JsonInputDialog( 68 | project: Project, 69 | val inputModelBlock: (inputModel: CollectInfo) -> Boolean 70 | ) : Messages.InputDialog( 71 | project, 72 | "Please input the class name and JSON String for generating dart bean class", 73 | "Make Dart bean Class Code", 74 | Messages.getInformationIcon(), 75 | "", 76 | myInputValidator 77 | ) { 78 | 79 | private lateinit var classNameInput: JTextField 80 | private var defaultValueContainer: JPanel? = null 81 | 82 | private val prettyGson: Gson = GsonBuilder().setPrettyPrinting().serializeNulls().create() 83 | 84 | init { 85 | setOKButtonText("Make") 86 | } 87 | 88 | override fun createMessagePanel(): JPanel { 89 | val messagePanel = JPanel(BorderLayout()) 90 | messagePanel.layout = VerticalFlowLayout() 91 | if (myMessage != null) { 92 | val textComponent = createTextComponent() 93 | messagePanel.add(textComponent) 94 | } 95 | myField = createTextFieldComponent() 96 | 97 | 98 | val classNameInputContainer = createLinearLayoutVertical() 99 | val classNameTitle = JBLabel("Class Name: ") 100 | classNameTitle.border = JBEmptyBorder(5, 0, 5, 0) 101 | classNameInputContainer.addComponentIntoVerticalBoxAlignmentLeft(classNameTitle) 102 | classNameInput = JTextField() 103 | classNameInput.preferredSize = JBDimension(400, 40) 104 | myInputValidator.classNameField = classNameInput 105 | 106 | classNameInput.document.addDocumentListener(object : DocumentAdapter() { 107 | override fun textChanged(e: DocumentEvent) { 108 | okAction.isEnabled = myInputValidator.checkInput(myField.text) 109 | } 110 | }) 111 | 112 | classNameInputContainer.addComponentIntoVerticalBoxAlignmentLeft(classNameInput) 113 | classNameInputContainer.preferredSize = JBDimension(500, 56) 114 | 115 | 116 | val createScrollableTextComponent = createMyScrollableTextComponent() 117 | val jsonInputContainer = createLinearLayoutVertical() 118 | jsonInputContainer.preferredSize = JBDimension(700, 400) 119 | jsonInputContainer.border = JBEmptyBorder(5, 0, 5, 5) 120 | val jsonTitle = JBLabel("JSON Text:") 121 | jsonTitle.border = JBEmptyBorder(5, 0, 5, 0) 122 | jsonInputContainer.addComponentIntoVerticalBoxAlignmentLeft(jsonTitle) 123 | jsonInputContainer.addComponentIntoVerticalBoxAlignmentLeft(createScrollableTextComponent) 124 | 125 | 126 | val centerContainer = JPanel() 127 | val centerBoxLayout = BoxLayout(centerContainer, BoxLayout.PAGE_AXIS) 128 | centerContainer.layout = centerBoxLayout 129 | centerContainer.addComponentIntoVerticalBoxAlignmentLeft(classNameInputContainer) 130 | centerContainer.addComponentIntoVerticalBoxAlignmentLeft(jsonInputContainer) 131 | messagePanel.add(centerContainer) 132 | 133 | //底部按钮栏 134 | 135 | messagePanel.add(settingsContainer()) 136 | 137 | //底部默认值 138 | defaultValueContainer = defaultValueContainer() 139 | messagePanel.add(defaultValueContainer) 140 | return messagePanel 141 | } 142 | 143 | private fun settingsContainer(): JPanel { 144 | val formatButton = JButton("Format") 145 | formatButton.horizontalAlignment = SwingConstants.CENTER 146 | formatButton.addActionListener(object : AbstractAction() { 147 | override fun actionPerformed(p0: ActionEvent?) { 148 | handleFormatJSONString() 149 | } 150 | }) 151 | val settingContainer = JPanel() 152 | settingContainer.border = JBEmptyBorder(0, 5, 5, 7) 153 | val boxLayout = BoxLayout(settingContainer, BoxLayout.LINE_AXIS) 154 | settingContainer.layout = boxLayout 155 | settingContainer.add(Box.createHorizontalGlue()) 156 | settingContainer.add(createCheckBox()) 157 | settingContainer.add(formatButton) 158 | return settingContainer 159 | } 160 | 161 | private fun defaultValueContainer(): JPanel { 162 | val settings = ApplicationManager.getApplication().getService(Settings::class.java) 163 | //string 164 | val defaultContainer = JPanel() 165 | defaultContainer.layout = BoxLayout(defaultContainer, BoxLayout.X_AXIS) 166 | buildDefaultItem("String", { newText -> 167 | settings.stringDefaultValue = newText 168 | }, settings.stringDefaultValue).forEach { 169 | defaultContainer.add(it) 170 | } 171 | buildDefaultItem("int", { newText -> 172 | settings.intDefaultValue = newText 173 | }, settings.intDefaultValue).forEach { 174 | defaultContainer.add(it) 175 | } 176 | buildDefaultItem("bool", { newText -> 177 | settings.boolDefaultValue = newText 178 | }, settings.boolDefaultValue).forEach { 179 | defaultContainer.add(it) 180 | } 181 | buildDefaultItem("List", { newText -> 182 | settings.listDefaultValue = newText 183 | }, settings.listDefaultValue).forEach { 184 | defaultContainer.add(it) 185 | } 186 | defaultContainer.add(Box.createHorizontalGlue()) 187 | return defaultContainer 188 | } 189 | 190 | private fun buildDefaultItem( 191 | title: String, 192 | newTextCallback: (newText: String) -> Unit, 193 | defaultValue: String 194 | ): List { 195 | val stringDefaultInput = JTextField(defaultValue) 196 | stringDefaultInput.preferredSize = JBDimension(50, 30) 197 | // stringDefaultInput.margin = JBUI.insetsRight(20) 198 | stringDefaultInput.document.addDocumentListener(object : DocumentAdapter() { 199 | override fun textChanged(e: DocumentEvent) { 200 | newTextCallback(stringDefaultInput.text) 201 | } 202 | }) 203 | val stringDefaultInputLabel = JLabel("${title}:") 204 | 205 | return listOf(stringDefaultInputLabel, stringDefaultInput) 206 | } 207 | 208 | override fun createTextFieldComponent(): JTextComponent { 209 | val jTextArea = JTextArea(15, 50) 210 | jTextArea.minimumSize = JBDimension(750, 400) 211 | // jTextArea.lineWrap = true 212 | // jTextArea.wrapStyleWord = true 213 | // jTextArea.autoscrolls = true 214 | return jTextArea 215 | } 216 | 217 | 218 | private fun createMyScrollableTextComponent(): JComponent { 219 | val jbScrollPane = JBScrollPane(myField) 220 | jbScrollPane.preferredSize = JBDimension(700, 350) 221 | jbScrollPane.autoscrolls = true 222 | jbScrollPane.horizontalScrollBarPolicy = JBScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED 223 | jbScrollPane.verticalScrollBarPolicy = JBScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED 224 | return jbScrollPane 225 | } 226 | 227 | 228 | override fun getPreferredFocusedComponent(): JComponent? { 229 | return if (classNameInput.text?.isEmpty() == false) { 230 | myField 231 | } else { 232 | classNameInput 233 | } 234 | } 235 | 236 | fun handleFormatJSONString() { 237 | val currentText = myField.text ?: "" 238 | if (currentText.isNotEmpty()) { 239 | try { 240 | val jsonElement = prettyGson.fromJson(currentText, JsonElement::class.java) 241 | val formatJSON = prettyGson.toJson(jsonElement) 242 | myField.text = formatJSON 243 | } catch (e: Exception) { 244 | } 245 | } 246 | 247 | feedBackFormatJSONActionInfo() 248 | 249 | } 250 | 251 | private fun feedBackFormatJSONActionInfo() { 252 | // Thread { sendActionInfo(prettyGson.toJson(FormatJSONAction())) }.start() 253 | } 254 | 255 | private fun createCheckBox(): DialogPanel { 256 | val listCheckBox = mutableListOf?>(null, null, null) 257 | return panel { 258 | row { 259 | listCheckBox[0] = 260 | checkBox( 261 | "null-able", 262 | ApplicationManager.getApplication().getService(Settings::class.java).isOpenNullAble == true 263 | ).apply { 264 | component.addItemListener { 265 | ApplicationManager.getApplication().getService(Settings::class.java).isOpenNullAble = 266 | component.isSelected 267 | } 268 | } 269 | listCheckBox[1] = 270 | checkBox( 271 | "default value", 272 | ApplicationManager.getApplication().getService(Settings::class.java).setDefault == true 273 | ).apply { 274 | component.addItemListener { 275 | defaultValueContainer?.isVisible = component.isSelected 276 | ApplicationManager.getApplication().getService(Settings::class.java).setDefault = 277 | component.isSelected 278 | } 279 | } 280 | } 281 | } 282 | } 283 | 284 | override fun doOKAction() { 285 | 286 | val collectInfo = CollectInfo().apply { 287 | userInputClassName = classNameInput.text 288 | userInputJson = myField.text 289 | } 290 | if (collectInfo.userInputClassName.isEmpty()) { 291 | throw Exception("className must not null or empty") 292 | } 293 | if (collectInfo.userInputJson.isEmpty()) { 294 | throw Exception("json must not null or empty") 295 | } 296 | 297 | if (inputModelBlock(collectInfo)) { 298 | super.doOKAction() 299 | } 300 | } 301 | } 302 | 303 | 304 | fun createLinearLayoutVertical(): JPanel { 305 | val container = JPanel() 306 | val boxLayout = BoxLayout(container, BoxLayout.PAGE_AXIS) 307 | container.layout = boxLayout 308 | return container 309 | } 310 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/ui/VerticalFlowLayout.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.ui 2 | 3 | import java.awt.* 4 | import java.awt.event.ActionEvent 5 | import java.io.Serializable 6 | import java.util.* 7 | import javax.swing.* 8 | import javax.swing.border.LineBorder 9 | import javax.swing.event.ChangeEvent 10 | 11 | /** 12 | * 容器宽度 = 容器左边框宽度 + 容器左边框与组件的间隙 + 组件宽度 ( + 组件间水平间隙 + 组件宽度 ) + 组件与容器右边框的间隙 + 容器右边框宽度 13 | * 容器高度 = 类似 14 | */ 15 | class VerticalFlowLayout // 折列时, 每列列宽是否相同 16 | // 折列时, 列是否填充满容器 17 | constructor(// 每一列中各组件水平向对齐方式(注意非每一列在容器中的水平向对齐方式, 因为每一列在容器中的水平对齐方式应当由 容器的 componentOrientation 属性 ltr/rtl 来指定) 18 | var hAlign: Int = LEFT, // 每一列在容器中的垂直向对齐方式(注意无每一列在容器中的水平向对齐方式) 19 | var vAlign: Int = TOP, // 水平向边框与组件之间的间隙 20 | var hPadding: Int = 5, // 垂直向边框与组件之间的间隙, TOP:顶边距, BOTTOM:底边距 21 | var vPadding: Int = 5, // 水平向组件之间的间隙 22 | var hGap: Int = 5, // 垂直向组件之间的间隙 23 | var vGap: Int = 5, // 水平向组件是否填满逻辑列的宽度 24 | var isFill: Boolean = true, // 是否折列, true:折列, false:固定一列 25 | var isWrap: Boolean = false 26 | ) : LayoutManager, Serializable { 27 | 28 | constructor(padding: Int, gap: Int) : this(LEFT, TOP, padding, padding, gap, gap, true, false) 29 | constructor(padding: Int) : this(LEFT, TOP, padding, padding, 5, 5, true, false) 30 | 31 | override fun addLayoutComponent(name: String, comp: Component) {} 32 | override fun removeLayoutComponent(comp: Component) {} 33 | 34 | /** 35 | * 最合适的尺寸, 一列放下全部组件 36 | */ 37 | override fun preferredLayoutSize(container: Container): Dimension { 38 | synchronized(container.treeLock) { 39 | var width = 0 40 | var height = 0 41 | 42 | // 可见组件的最大宽和累计高 43 | val components = getVisibleComponents(container) 44 | for (component in components) { 45 | val dimension = component.preferredSize 46 | width = Math.max(width, dimension.width) 47 | height += dimension.height 48 | } 49 | 50 | // 累计高添加组件间间隙 51 | if (0 < components.size) { 52 | height += vGap * (components.size - 1) 53 | } 54 | 55 | // 累计宽高添加边框宽高 56 | val insets = container.insets 57 | width += insets.left + insets.right 58 | height += insets.top + insets.bottom 59 | 60 | // 有组件的话, 累计宽高添加边框与组件的间隙和 61 | if (0 < components.size) { 62 | width += hPadding * 2 63 | height += vPadding * 2 64 | } 65 | return Dimension(width, height) 66 | } 67 | } 68 | 69 | override fun minimumLayoutSize(parent: Container): Dimension { 70 | synchronized(parent.treeLock) { 71 | var width = 0 72 | var height = 0 73 | 74 | // 可见组件的最大宽和累计高 75 | val components = getVisibleComponents(parent) 76 | for (component in components) { 77 | val dimension = component.minimumSize 78 | width = Math.max(width, dimension.width) 79 | height += dimension.height 80 | } 81 | 82 | // 累计高添加组件间间隙 83 | if (0 < components.size) { 84 | height += vGap * (components.size - 1) 85 | } 86 | 87 | // 累计宽高添加边框宽高 88 | val insets = parent.insets 89 | width += insets.left + insets.right 90 | height += insets.top + insets.bottom 91 | 92 | // 有组件的话, 累计宽高添加边框与组件的间隙和 93 | if (0 < components.size) { 94 | width += hPadding * 2 95 | height += vPadding * 2 96 | } 97 | return Dimension(width, height) 98 | } 99 | } 100 | 101 | override fun layoutContainer(container: Container) { 102 | synchronized(container.treeLock) { 103 | 104 | 105 | // 容器理想宽高, 即组件加间隙加容器边框后的累积理想宽高, 用于和容器实际宽高做比较 106 | val idealSize = preferredLayoutSize(container) 107 | // 容器实际宽高 108 | val size = container.size 109 | // 容器实际边框 110 | val insets = container.insets 111 | 112 | // 容器内可供组件使用的空间大小(排除边框和内边距) 113 | val availableWidth = size.width - insets.left - insets.right - hPadding * 2 114 | val availableHeight = size.height - insets.top - insets.bottom - vPadding * 2 115 | 116 | // 容器定义的组件方向, 这里先不管, 默认从左往右 117 | // ComponentOrientation orientation = container.getComponentOrientation(); 118 | 119 | // 容器内所有可见组件 120 | val components = getVisibleComponents(container) 121 | 122 | // x基点 123 | var xBase = insets.left + hPadding 124 | 125 | // 缓存当前列中的所有组件 126 | val list: MutableList = LinkedList() 127 | for (component in components) { 128 | list.add(component) 129 | 130 | // 预算判断 131 | // 换列标准: 允许换列 且 该列组件数>1 且 该列累积高>容器可用高+vPadding 132 | // 累积高: 算上当前组件后, 当前列中的组件的累加高度(组件高度+组件间隙) 133 | if (isWrap && list.size > 1 && availableHeight + vPadding < getPreferredHeight(list)) { 134 | 135 | // 如果需要换行, 则当前列中得移除当前组件 136 | list.remove(component) 137 | batch(insets, availableWidth, availableHeight, xBase, list, components) 138 | xBase += hGap + getPreferredWidth(list) 139 | 140 | // 需要换列, 清空上一列中的所有组件 141 | list.clear() 142 | list.add(component) 143 | } 144 | } 145 | if (list.isNotEmpty()) { 146 | batch(insets, availableWidth, availableHeight, xBase, list, components) 147 | } 148 | } 149 | } 150 | 151 | private fun batch( 152 | insets: Insets, 153 | availableWidth: Int, 154 | availableHeight: Int, 155 | xBase: Int, 156 | list: List, 157 | components: List 158 | ) { 159 | val preferredWidth = getPreferredWidth(list) 160 | val preferredHeight = getPreferredHeight(list) 161 | 162 | // y 163 | var y: Int 164 | y = if (vAlign == TOP) { 165 | insets.top + vPadding 166 | } else if (vAlign == CENTER) { 167 | (availableHeight - preferredHeight) / 2 + insets.top + vPadding 168 | } else if (vAlign == BOTTOM) { 169 | availableHeight - preferredHeight + insets.top + vPadding 170 | } else { 171 | insets.top + vPadding 172 | } 173 | for (i in list.indices) { 174 | val item = list[i] 175 | 176 | // x 177 | val x: Int = if (isFill) { 178 | xBase 179 | } else { 180 | when (hAlign) { 181 | LEFT -> { 182 | xBase 183 | } 184 | 185 | CENTER -> { 186 | xBase + (preferredWidth - item.preferredSize.width) / 2 187 | } 188 | 189 | RIGHT -> { 190 | xBase + preferredWidth - item.preferredSize.width 191 | } 192 | 193 | else -> { 194 | xBase 195 | } 196 | } 197 | } 198 | 199 | // width 200 | var width: Int 201 | if (isFill) { 202 | width = if (isWrap) preferredWidth else availableWidth 203 | // 下面这个判断的效果: 允许填充 且 允许折列 且 只有1列时, 填充全部可用区域 204 | // 或许可以来一个 开关 专门设置是否开启这个配置 205 | if (list.size == components.size) { 206 | width = availableWidth 207 | } 208 | } else { 209 | width = item.preferredSize.width 210 | } 211 | 212 | // y 213 | if (i != 0) { 214 | y += vGap 215 | } 216 | 217 | // 组件调整 218 | item.setBounds(x, y, width, item.preferredSize.height) 219 | 220 | // y 221 | y += item.height 222 | } 223 | } 224 | 225 | private fun getVisibleComponents(container: Container): List { 226 | val list: MutableList = ArrayList() 227 | for (component in container.components) { 228 | if (component.isVisible) { 229 | list.add(component) 230 | } 231 | } 232 | return list 233 | } 234 | 235 | private fun getPreferredWidth(components: List): Int { 236 | var width = 0 237 | for (component in components) { 238 | width = Math.max(width, component.preferredSize.width) 239 | } 240 | return width 241 | } 242 | 243 | private fun getPreferredHeight(components: List): Int { 244 | var height = 0 245 | // 可见组件的最大宽和累计高 246 | for (component in components) { 247 | height += component.preferredSize.height 248 | } 249 | // 累计高添加组件间间隙 250 | if (0 < components.size) { 251 | height += vGap * (components.size - 1) 252 | } 253 | return height 254 | } 255 | 256 | override fun toString(): String { 257 | return "VerticalFlowLayout{" + 258 | "hAlign=" + hAlign + 259 | ", vAlign=" + vAlign + 260 | ", hPadding=" + hPadding + 261 | ", vPadding=" + vPadding + 262 | ", hGap=" + hGap + 263 | ", vGap=" + vGap + 264 | ", fill=" + isFill + 265 | ", wrap=" + isWrap + 266 | '}' 267 | } 268 | 269 | companion object { 270 | private const val serialVersionUID = 1L 271 | const val CENTER = 0 // 垂直对齐/水平对齐 272 | const val TOP = 1 // 垂直对齐 273 | const val BOTTOM = 2 // 垂直对齐 274 | const val LEFT = 3 // 水平对齐 275 | const val RIGHT = 4 // 水平对齐 276 | 277 | @JvmStatic 278 | fun main(args: Array) { 279 | 280 | // 推荐一套超级漂亮的UI 281 | // FlatLaf:干净、优雅、扁平化的现代开源跨平台外观 282 | // https://weibo.com/ttarticle/p/show?id=2309404704477499490781 283 | // 官方: https://www.formdev.com/flatlaf/ 284 | // 分两个包, 核心包和扩展主题包, 核心包自带4个主题, 使用如下代码启用对应主题 285 | // FlatIntelliJLaf.setup(); 286 | // FlatLightLaf.setup(); 287 | // FlatDarculaLaf.setup(); 288 | // FlatDarkLaf.setup(); 289 | val frame = JFrame("VerticalFlowLayout Test") 290 | frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE 291 | val panel = JPanel() 292 | frame.contentPane.add(panel, BorderLayout.CENTER) 293 | panel.border = LineBorder(Color.white, 10) 294 | val layout = VerticalFlowLayout() 295 | panel.layout = layout 296 | panel.add(JButton("00000000000000000000000000000000000000000000000000")) 297 | panel.add(JButton("1")) 298 | panel.add(JButton("22")) 299 | panel.add(JButton("333")) 300 | panel.add(JButton("4444")) 301 | panel.add(JButton("55555")) 302 | panel.add(JButton("666666")) 303 | panel.add(JButton("7777777")) 304 | panel.add(JButton("88888888")) 305 | panel.add(JButton("999999999999999999999999999999999999999999999")) 306 | val border = LineBorder(Color.gray, 1) 307 | val label = JLabel("hello world") 308 | label.border = border 309 | panel.add(label) 310 | val radioButton = JRadioButton("select me") 311 | radioButton.border = border 312 | panel.add(radioButton) 313 | val checkBox = JCheckBox("select me") 314 | checkBox.border = border 315 | panel.add(checkBox) 316 | val textField = JTextField() 317 | textField.border = border 318 | panel.add(textField) 319 | val label2 = JLabel("hello world") 320 | label2.border = border 321 | panel.add(label2) 322 | val control = JPanel() 323 | control.layout = VerticalFlowLayout() 324 | frame.contentPane.add(control, BorderLayout.SOUTH) 325 | val borderPanel = JPanel() 326 | control.add(borderPanel) 327 | borderPanel.layout = FlowLayout(FlowLayout.LEFT) 328 | borderPanel.add(JLabel("border")) 329 | val borderSpinner = JSpinner(SpinnerNumberModel(10, 0, 100, 5)) 330 | borderPanel.add(borderSpinner) 331 | borderSpinner.addChangeListener { e: ChangeEvent? -> 332 | panel.border = LineBorder(Color.white, borderSpinner.value as Int) 333 | panel.revalidate() 334 | } 335 | val hAlignPanel = JPanel() 336 | control.add(hAlignPanel) 337 | hAlignPanel.layout = FlowLayout(FlowLayout.LEFT) 338 | val hAlign = ButtonGroup() 339 | val hLeft = JRadioButton("Left") 340 | hLeft.isSelected = true 341 | hLeft.addActionListener { e: ActionEvent? -> 342 | layout.hAlign = LEFT 343 | panel.revalidate() 344 | println(layout) 345 | } 346 | hAlign.add(hLeft) 347 | val hCenter = JRadioButton("Center") 348 | hCenter.addActionListener { e: ActionEvent? -> 349 | layout.hAlign = CENTER 350 | panel.revalidate() 351 | println(layout) 352 | } 353 | hAlign.add(hCenter) 354 | val hRight = JRadioButton("Right") 355 | hRight.addActionListener { e: ActionEvent? -> 356 | layout.hAlign = RIGHT 357 | panel.revalidate() 358 | println(layout) 359 | } 360 | hAlign.add(hRight) 361 | hAlignPanel.add(JLabel("hAlign")) 362 | hAlignPanel.add(hLeft) 363 | hAlignPanel.add(hCenter) 364 | hAlignPanel.add(hRight) 365 | val vAlignPanel = JPanel() 366 | control.add(vAlignPanel) 367 | vAlignPanel.layout = FlowLayout(FlowLayout.LEFT) 368 | val vAlign = ButtonGroup() 369 | val vTop = JRadioButton("Top") 370 | vTop.isSelected = true 371 | vTop.addActionListener { e: ActionEvent? -> 372 | layout.vAlign = TOP 373 | panel.revalidate() 374 | println(layout) 375 | } 376 | vAlign.add(vTop) 377 | val vCenter = JRadioButton("Center") 378 | vCenter.addActionListener { e: ActionEvent? -> 379 | layout.vAlign = CENTER 380 | panel.revalidate() 381 | println(layout) 382 | } 383 | vAlign.add(vCenter) 384 | val vBottom = JRadioButton("Bottom") 385 | vBottom.addActionListener { e: ActionEvent? -> 386 | layout.vAlign = BOTTOM 387 | panel.revalidate() 388 | println(layout) 389 | } 390 | vAlign.add(vBottom) 391 | vAlignPanel.add(JLabel("vAlign")) 392 | vAlignPanel.add(vTop) 393 | vAlignPanel.add(vCenter) 394 | vAlignPanel.add(vBottom) 395 | val hPaddingPanel = JPanel() 396 | control.add(hPaddingPanel) 397 | hPaddingPanel.layout = FlowLayout(FlowLayout.LEFT) 398 | hPaddingPanel.add(JLabel("hPadding")) 399 | val hPaddingSpinner = JSpinner(SpinnerNumberModel(5, 0, 100, 5)) 400 | hPaddingPanel.add(hPaddingSpinner) 401 | hPaddingSpinner.addChangeListener { e: ChangeEvent? -> 402 | layout.hPadding = hPaddingSpinner.value as Int 403 | panel.revalidate() 404 | println(layout) 405 | } 406 | val vPaddingPanel = JPanel() 407 | control.add(vPaddingPanel) 408 | vPaddingPanel.layout = FlowLayout(FlowLayout.LEFT) 409 | vPaddingPanel.add(JLabel("vPadding")) 410 | val vPaddingSpinner = JSpinner(SpinnerNumberModel(5, 0, 100, 5)) 411 | vPaddingPanel.add(vPaddingSpinner) 412 | vPaddingSpinner.addChangeListener { e: ChangeEvent? -> 413 | layout.vPadding = vPaddingSpinner.value as Int 414 | panel.revalidate() 415 | println(layout) 416 | } 417 | val hGapPanel = JPanel() 418 | control.add(hGapPanel) 419 | hGapPanel.layout = FlowLayout(FlowLayout.LEFT) 420 | hGapPanel.add(JLabel("hGap")) 421 | val hGapSpinner = JSpinner(SpinnerNumberModel(5, 0, 100, 5)) 422 | hGapPanel.add(hGapSpinner) 423 | hGapSpinner.addChangeListener { e: ChangeEvent? -> 424 | layout.hGap = hGapSpinner.value as Int 425 | panel.revalidate() 426 | println(layout) 427 | } 428 | val vGapPanel = JPanel() 429 | control.add(vGapPanel) 430 | vGapPanel.layout = FlowLayout(FlowLayout.LEFT) 431 | vGapPanel.add(JLabel("vGap")) 432 | val vGapSpinner = JSpinner(SpinnerNumberModel(5, 0, 100, 5)) 433 | vGapPanel.add(vGapSpinner) 434 | vGapSpinner.addChangeListener { e: ChangeEvent? -> 435 | layout.vGap = vGapSpinner.value as Int 436 | panel.revalidate() 437 | println(layout) 438 | } 439 | val fillPanel = JPanel() 440 | control.add(fillPanel) 441 | fillPanel.layout = FlowLayout(FlowLayout.LEFT) 442 | val fillGroup = ButtonGroup() 443 | val fillTrue = JRadioButton("true") 444 | fillTrue.isSelected = true 445 | fillGroup.add(fillTrue) 446 | fillTrue.addActionListener { e: ActionEvent? -> 447 | layout.isFill = true 448 | panel.revalidate() 449 | println(layout) 450 | } 451 | val fillFalse = JRadioButton("false") 452 | fillGroup.add(fillFalse) 453 | fillFalse.addActionListener { e: ActionEvent? -> 454 | layout.isFill = false 455 | panel.revalidate() 456 | println(layout) 457 | } 458 | fillPanel.add(JLabel("fill")) 459 | fillPanel.add(fillTrue) 460 | fillPanel.add(fillFalse) 461 | val wrapPanel = JPanel() 462 | control.add(wrapPanel) 463 | wrapPanel.layout = FlowLayout(FlowLayout.LEFT) 464 | val wrapGroup = ButtonGroup() 465 | val wrapTrue = JRadioButton("true") 466 | wrapGroup.add(wrapTrue) 467 | wrapTrue.addActionListener { e: ActionEvent? -> 468 | layout.isWrap = true 469 | panel.revalidate() 470 | println(layout) 471 | } 472 | val wrapFalse = JRadioButton("false") 473 | wrapFalse.isSelected = true 474 | wrapGroup.add(wrapFalse) 475 | wrapFalse.addActionListener { e: ActionEvent? -> 476 | layout.isWrap = false 477 | panel.revalidate() 478 | println(layout) 479 | } 480 | wrapPanel.add(JLabel("wrap")) 481 | wrapPanel.add(wrapTrue) 482 | wrapPanel.add(wrapFalse) 483 | frame.pack() 484 | frame.setSize(frame.size.width + 1000, frame.size.height + 200) 485 | frame.setLocationRelativeTo(null) 486 | frame.isVisible = true 487 | println(layout) 488 | } 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/utils/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.utils 2 | 3 | import com.google.gson.JsonArray 4 | import com.intellij.ide.plugins.PluginUtil 5 | import com.intellij.notification.* 6 | import com.intellij.notification.impl.NotificationGroupEP 7 | import com.intellij.openapi.application.ApplicationManager 8 | import com.intellij.openapi.project.Project 9 | import com.intellij.openapi.ui.Messages 10 | import java.awt.Component 11 | import java.awt.Container 12 | import java.util.regex.Pattern 13 | import javax.swing.Box 14 | import javax.swing.BoxLayout 15 | 16 | 17 | /** 18 | * 19 | * Created by Seal.Wu on 2017/9/25. 20 | */ 21 | 22 | fun Container.addComponentIntoVerticalBoxAlignmentLeft(component: Component) { 23 | if (layout is BoxLayout) { 24 | 25 | val hBox = Box.createHorizontalBox() 26 | hBox.add(component) 27 | hBox.add(Box.createHorizontalGlue()) 28 | add(hBox) 29 | } 30 | 31 | } 32 | 33 | fun Container.addComponentIntoVerticalBoxAlignmentLeft(component: Component, leftMargin: Int) { 34 | if (layout is BoxLayout) { 35 | 36 | val hBox = Box.createHorizontalBox() 37 | hBox.add(Box.createHorizontalStrut(leftMargin)) 38 | hBox.add(component) 39 | hBox.add(Box.createHorizontalGlue()) 40 | add(hBox) 41 | } 42 | 43 | } 44 | 45 | 46 | /** 47 | * How many substring in the parent string 48 | */ 49 | fun String.numberOf(subString: String): Int { 50 | var count = 0 51 | val pattern = Pattern.compile(subString) 52 | val matcher = pattern.matcher(this) 53 | while (matcher.find()) { 54 | count++ 55 | } 56 | return count 57 | } 58 | 59 | /** 60 | * array only has one element 61 | */ 62 | private fun JsonArray.onlyHasOneElement(): Boolean { 63 | return size() == 1 64 | } 65 | 66 | /** 67 | * array only has one object element 68 | */ 69 | private fun JsonArray.onlyHasOneObjectElement(): Boolean { 70 | return size() == 1 && get(0).isJsonObject 71 | } 72 | 73 | /** 74 | * array only has object element 75 | */ 76 | private fun JsonArray.allObjectElement(): Boolean { 77 | forEach { 78 | if (it.isJsonObject.not()) { 79 | return false 80 | } 81 | } 82 | return true 83 | } 84 | 85 | /** 86 | * if Multidimensional Arrays only has one element 87 | */ 88 | fun JsonArray.onlyHasOneElementRecursive(): Boolean { 89 | 90 | if (size() == 0) { 91 | return false 92 | } 93 | if (onlyHasOneElement().not()) { 94 | return false 95 | } 96 | 97 | if (get(0).isJsonPrimitive || get(0).isJsonObject || get(0).isJsonNull) { 98 | return true 99 | } 100 | 101 | return get(0).asJsonArray.onlyHasOneElementRecursive() 102 | } 103 | 104 | 105 | /** 106 | * if Multidimensional Arrays only has one element 107 | */ 108 | fun JsonArray.onlyHasOneObjectElementRecursive(): Boolean { 109 | 110 | if (size() == 0) { 111 | return false 112 | } 113 | if (onlyHasOneElement().not()) { 114 | return false 115 | } 116 | 117 | if (get(0).isJsonPrimitive || get(0).isJsonNull) { 118 | return false 119 | } 120 | 121 | if (get(0).isJsonObject) { 122 | return true 123 | } 124 | return get(0).asJsonArray.onlyHasOneObjectElementRecursive() 125 | } 126 | 127 | 128 | /** 129 | * if Multidimensional Arrays only has one dimension contains element and the elements are all object element 130 | */ 131 | fun JsonArray.onlyOneSubArrayContainsElementAndAllObjectRecursive(): Boolean { 132 | 133 | if (size() == 0) { 134 | return false 135 | } 136 | 137 | if (get(0).isJsonPrimitive || get(0).isJsonNull) { 138 | return false 139 | } 140 | 141 | if (allObjectElement()) { 142 | return true 143 | } 144 | 145 | return get(0).asJsonArray.onlyOneSubArrayContainsElementAndAllObjectRecursive() 146 | } 147 | 148 | 149 | fun Project.showNotify(notifyMessage: String) { 150 | try { 151 | 152 | NotificationGroupManager.getInstance() 153 | .getNotificationGroup("flutterJsonNotification") 154 | .createNotification(notifyMessage, NotificationType.INFORMATION) 155 | .notify(this) 156 | 157 | } catch (e: Exception) { 158 | e.printStackTrace() 159 | } 160 | } 161 | 162 | fun Project.showErrorMessage(notifyMessage: String) { 163 | Messages.showInfoMessage(this, notifyMessage, "Info"); 164 | } 165 | 166 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/utils/FieldUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.utils 2 | 3 | import io.flutter.FlutterUtils 4 | 5 | object FieldUtils { 6 | /** 7 | * 把所有符号换成下划线,再把下换线后一位转成大写 8 | */ 9 | fun toFieldTypeName(name: String): String { 10 | val init = name.replace(Regex("[^a-zA-Z0-9_]"), "_") 11 | val newInit = init.replace("-", "_") 12 | if (newInit.contains("_").not()) { 13 | return newInit.toUpperCaseFirstOne() 14 | } 15 | val ret = StringBuilder(newInit.length) 16 | for (word in newInit.split("_".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) { 17 | if (word.isNotEmpty()) { 18 | ret.append(word.substring(0, 1).toUpperCase()) 19 | ret.append(word.substring(1).toLowerCase()) 20 | } 21 | if (ret.length != newInit.length) 22 | ret.append(" ") 23 | } 24 | 25 | /* if (PRIMITIVE_TYPES[result] != null || dartKeyword.contains(result)) { 26 | // throw MessageException("Please do not use the keyword $result as the key") 27 | result +="X" 28 | }*/ 29 | return filedKeywordRename(ret.toString().replace(" ", "")) 30 | } 31 | 32 | 33 | private fun filedKeywordRename(key: String): String { 34 | var notKeyWord = key 35 | //关键字的修改字段名 36 | if (FlutterUtils.isDartKeyword(key) || key.first().isDigit()) { 37 | notKeyWord = "x${key.toUpperCaseFirstOne()}" 38 | } 39 | return notKeyWord 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/utils/GsonUtil.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.utils 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import com.google.gson.TypeAdapter 6 | import java.io.IOException 7 | import com.google.gson.stream.JsonReader 8 | import com.google.gson.internal.LinkedTreeMap 9 | import com.google.gson.reflect.TypeToken 10 | import com.google.gson.stream.JsonToken 11 | import com.google.gson.stream.JsonWriter 12 | import java.lang.IllegalStateException 13 | import java.util.ArrayList 14 | 15 | /** 16 | * User: zhangruiyu 17 | * Date: 2019/12/24 18 | * Time: 21:19 19 | */ 20 | object GsonUtil { 21 | /** 22 | * 实现格式化的时间字符串转时间对象 23 | */ 24 | private const val DATEFORMAT_default = "yyyy-MM-dd HH:mm:ss" 25 | 26 | /** 27 | * 使用默认的gson对象进行反序列化 28 | * 29 | * @param json 30 | * @param typeToken 31 | * @return 32 | */ 33 | fun fromJsonDefault(json: String?, typeToken: TypeToken): T { 34 | val gson = Gson() 35 | return gson.fromJson(json, typeToken.type) 36 | } 37 | 38 | /** 39 | * json字符串转list或者map 40 | * 41 | * @param json 42 | * @param typeToken 43 | * @return 44 | */ 45 | fun fromJson(json: String?, typeToken: TypeToken): T { 46 | val gson = GsonBuilder() 47 | /** 48 | * 重写map的反序列化 49 | */ 50 | .registerTypeAdapter(object : TypeToken?>() {}.type, MapTypeAdapter()).create() 51 | return gson.fromJson(json, typeToken.type) 52 | } 53 | 54 | /** 55 | * json字符串转bean对象 56 | * 57 | * @param json 58 | * @param cls 59 | * @return 60 | */ 61 | fun fromJson(json: String?, cls: Class?): T { 62 | val gson = GsonBuilder().setDateFormat(DATEFORMAT_default) 63 | .create() 64 | return gson.fromJson(json, cls) 65 | } 66 | 67 | /** 68 | * 对象转json 69 | * 70 | * @param obj 71 | * @param format 72 | * @return 73 | */ 74 | fun toJson(obj: Any?, format: Boolean): String { 75 | val gsonBuilder = GsonBuilder() 76 | /** 77 | * 设置默认时间格式 78 | */ 79 | gsonBuilder.setDateFormat(DATEFORMAT_default) 80 | /** 81 | * 添加格式化设置 82 | */ 83 | if (format) { 84 | gsonBuilder.setPrettyPrinting() 85 | } 86 | val gson = gsonBuilder.create() 87 | return gson.toJson(obj) 88 | } 89 | 90 | class MapTypeAdapter : TypeAdapter() { 91 | @Throws(IOException::class) 92 | override fun read(`in`: JsonReader): Any? { 93 | val token = `in`.peek() 94 | return when (token) { 95 | JsonToken.BEGIN_ARRAY -> { 96 | val list: MutableList = ArrayList() 97 | `in`.beginArray() 98 | while (`in`.hasNext()) { 99 | list.add(read(`in`)) 100 | } 101 | `in`.endArray() 102 | list 103 | } 104 | JsonToken.BEGIN_OBJECT -> { 105 | val map: MutableMap = 106 | LinkedTreeMap() 107 | `in`.beginObject() 108 | while (`in`.hasNext()) { 109 | map[`in`.nextName()] = read(`in`) 110 | } 111 | `in`.endObject() 112 | map 113 | } 114 | JsonToken.STRING -> `in`.nextString() 115 | JsonToken.NUMBER -> { 116 | /** 117 | * 改写数字的处理逻辑,将数字值分为整型与浮点型。 118 | */ 119 | val dbNum = `in`.nextString() 120 | if (!dbNum.contains(".")) { 121 | //返回0是int 122 | 0 123 | } else { 124 | //返回double类型代表是double类型 125 | 0.0 126 | } 127 | } 128 | JsonToken.BOOLEAN -> `in`.nextBoolean() 129 | JsonToken.NULL -> { 130 | `in`.nextNull() 131 | null 132 | } 133 | else -> throw IllegalStateException() 134 | } 135 | } 136 | 137 | @Throws(IOException::class) 138 | override fun write(out: JsonWriter, value: Any?) { 139 | // 序列化无需实现 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/utils/JsonUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.utils 2 | 3 | import com.google.gson.JsonArray 4 | import com.google.gson.JsonObject 5 | import com.google.gson.internal.LinkedTreeMap 6 | 7 | /** 8 | * 处理json的list第一个不完整生成的json dart bean 9 | * 方案:遍历复制list的所有字段到第一个里 10 | */ 11 | class JsonUtils { 12 | companion object { 13 | fun jsonMapMCompletion(data: Any?, initData: MutableMap? = null): Any? { 14 | if (data == null) return null 15 | val returnData = initData ?: mutableMapOf() 16 | when (data) { 17 | is Map<*, *> -> { 18 | data.forEach { 19 | val key = it.key as String 20 | //如果包含,并且字段不为null,那么就不去修改,如果包含,但是包含的是null,那么以最后一个做标准 21 | if ((returnData.containsKey(key) && returnData[key] != null).not()) { 22 | returnData[it.key as String] = jsonMapMCompletion(it.value) 23 | } 24 | } 25 | return returnData 26 | } 27 | is List<*> -> { 28 | if (data.isEmpty()) return listOf() 29 | return if (data.first() is Map<*, *>) { 30 | //这个map中只有一个数据,但是map数据是最完整的 31 | val listNewDataMap = mutableMapOf() 32 | data.forEach { 33 | //如果是map,手写一个map,然后元数据每个map都遍历到这个值中,判断null和字段存在,主要为了防止map中字段不同意造成的json类不完整问题 34 | if (it is Map<*, *>) { 35 | //此时 36 | jsonMapMCompletion(it, listNewDataMap) 37 | } 38 | } 39 | listOf(listNewDataMap) 40 | } else { 41 | data 42 | } 43 | 44 | } 45 | else -> { 46 | return data 47 | } 48 | } 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/utils/LogUtil.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.utils 2 | 3 | import com.intellij.openapi.diagnostic.LoggerRt 4 | import com.github.zhangruiyu.flutterjsonbeanfactory.PLUGIN_NAME 5 | 6 | /** 7 | * Created by Seal.Wu on 2018/3/12. 8 | */ 9 | object LogUtil { 10 | 11 | fun i(info: String) { 12 | LoggerRt.getInstance(PLUGIN_NAME).info(info) 13 | } 14 | 15 | fun w(warning: String) { 16 | LoggerRt.getInstance(PLUGIN_NAME).warn(warning) 17 | } 18 | 19 | fun e(message: String, e: Throwable) { 20 | LoggerRt.getInstance(PLUGIN_NAME).error(message, e) 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/utils/SimplifiedMethods.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.utils 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.command.CommandProcessor 5 | import com.intellij.openapi.project.Project 6 | 7 | /** 8 | * File contains functions which simply other functions's invoke 9 | * Created by Seal.Wu on 2018/2/7. 10 | */ 11 | 12 | 13 | /** 14 | * do the action that could be roll-back 15 | */ 16 | fun Project?.executeCouldRollBackAction( action: (Project?) -> Unit) { 17 | CommandProcessor.getInstance().executeCommand(this, { 18 | ApplicationManager.getApplication().runWriteAction { 19 | action.invoke(this) 20 | } 21 | }, "FlutterJsonBeanFactory", "FlutterJsonBeanFactory") 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/utils/StringExt.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.utils 2 | 3 | import java.util.regex.Pattern 4 | 5 | //首字母转大写 6 | fun String.toUpperCaseFirstOne(): String { 7 | return when { 8 | isEmpty() -> "" 9 | Character.isUpperCase(this[0]) -> this 10 | else -> StringBuilder().append(Character.toUpperCase(this[0])).append(this.substring(1)).toString() 11 | } 12 | } 13 | 14 | //首字母转小写 15 | fun String.toLowerCaseFirstOne(): String { 16 | return if (Character.isLowerCase(this[0])) 17 | this 18 | else 19 | StringBuilder().append(Character.toLowerCase(this[0])).append(this.substring(1)).toString() 20 | } 21 | 22 | //大写字母转下划线和小写 23 | fun String.upperCharToUnderLine(): String { 24 | val p = Pattern.compile("[A-Z]") 25 | if (this == "") { 26 | return "" 27 | } 28 | val builder = StringBuilder(this) 29 | val mc = p.matcher(this) 30 | var i = 0 31 | while (mc.find()) { 32 | builder.replace(mc.start() + i, mc.end() + i, "_" + mc.group().toLowerCase()) 33 | i++ 34 | } 35 | if ('_' == builder[0]) { 36 | builder.deleteCharAt(0) 37 | } 38 | return builder.toString() 39 | } 40 | 41 | //下划线和小写转大写字母 42 | fun upperTable(str: String): String { 43 | // 字符串缓冲区 44 | val sbf = StringBuffer() 45 | // 如果字符串包含 下划线 46 | if (str.contains("_")) { 47 | // 按下划线来切割字符串为数组 48 | val split = str.split("_".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 49 | // 循环数组操作其中的字符串 50 | var i = 0 51 | val index = split.size 52 | while (i < index) { 53 | // 递归调用本方法 54 | val upperTable = upperTable(split[i]) 55 | // 添加到字符串缓冲区 56 | sbf.append(upperTable) 57 | i++ 58 | } 59 | } else {// 字符串不包含下划线 60 | // 转换成字符数组 61 | val ch = str.toCharArray() 62 | // 判断首字母是否是字母 63 | if (ch[0] in 'a'..'z') { 64 | // 利用ASCII码实现大写 65 | ch[0] = (ch[0].toInt() - 32).toChar() 66 | } 67 | // 添加进字符串缓存区 68 | sbf.append(ch) 69 | } 70 | // 返回 71 | return sbf.toString() 72 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/utils/VirtualFileExt.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.utils 2 | 3 | import com.intellij.openapi.command.CommandProcessor 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.vfs.VirtualFile 6 | import com.intellij.psi.PsiDocumentManager 7 | import com.intellij.psi.PsiManager 8 | import com.intellij.psi.codeStyle.CodeStyleManager 9 | 10 | /** 11 | *判断文件内容是否一致 不一致则覆盖 12 | */ 13 | fun VirtualFile?.commitContent(project: Project, content: String) { 14 | if (this != null) { 15 | val documentManager = PsiDocumentManager.getInstance(project) 16 | val psiManager = PsiManager.getInstance(project) 17 | psiManager.findFile(this)?.run { 18 | documentManager.getDocument(this)?.let { document -> 19 | if (document.text != content) { 20 | document.setText(content) 21 | documentManager.commitDocument(document) 22 | ///格式化下代码,异步执行防止卡顿 23 | CommandProcessor.getInstance().runUndoTransparentAction { 24 | CodeStyleManager.getInstance(project).reformat(this) 25 | } 26 | } else { 27 | LogUtil.i("${this.name}内容一致,无需修改和格式化") 28 | } 29 | } 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/utils/YamlHelper.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.utils 2 | 3 | import com.github.zhangruiyu.flutterjsonbeanfactory.file.FileHelpers 4 | import com.intellij.openapi.application.ApplicationManager 5 | import com.intellij.openapi.command.CommandProcessor 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.vfs.VirtualFile 8 | import com.intellij.psi.PsiDocumentManager 9 | import com.intellij.psi.PsiManager 10 | import com.intellij.psi.codeStyle.CodeStyleManager 11 | import io.flutter.pub.PubRoot 12 | import io.flutter.utils.FlutterModuleUtils 13 | import org.yaml.snakeyaml.Yaml 14 | import java.io.FileInputStream 15 | 16 | 17 | object YamlHelper { 18 | 19 | 20 | fun getPubSpecConfig(project: Project): PubSpecConfig? { 21 | PubRoot.forFile(FileHelpers.getProjectIdeaFile(project))?.let { pubRoot -> 22 | FileInputStream(pubRoot.pubspec.path).use { inputStream -> 23 | (Yaml().load(inputStream) as? Map)?.let { map -> 24 | return PubSpecConfig(project, pubRoot, map) 25 | } 26 | } 27 | } 28 | return null 29 | } 30 | 31 | @Suppress("DuplicatedCode") 32 | @JvmStatic 33 | fun shouldActivateFor(project: Project): Boolean = shouldActivateWith(getPubSpecConfig(project)) 34 | 35 | @Suppress("DuplicatedCode") 36 | @JvmStatic 37 | fun shouldActivateWith(pubSpecConfig: PubSpecConfig?): Boolean { 38 | return pubSpecConfig?.pubRoot?.declaresFlutter() ?: false 39 | } 40 | 41 | } 42 | 43 | 44 | @Suppress("SameParameterValue") 45 | /** 46 | * 如果没有配置,那么默认是true 47 | */ 48 | private fun isOptionTrue(map: Map<*, *>?, name: String): Boolean { 49 | val value = map?.get(name)?.toString()?.toLowerCase() ?: "true" 50 | return "true" == value 51 | } 52 | 53 | @Suppress("SameParameterValue") 54 | private fun isOptionFalse(map: Map<*, *>?, name: String): Boolean { 55 | val value = map?.get(name)?.toString()?.toLowerCase() 56 | return "false" == value 57 | } 58 | 59 | 60 | private fun optionString(map: Map<*, *>?, name: String, default: String): String { 61 | return map?.get(name)?.toString() ?: default 62 | } 63 | 64 | 65 | private const val PUBSPEC_KEY = "flutter_json" 66 | private const val PROJECT_NAME = "name" 67 | private const val PUBSPEC_ENABLE_PLUGIN_KEY = "enable" 68 | private const val PUBSPEC_GENERATED_PATH_KEY = "generated_path" 69 | private const val PUBSPEC_DART_ENABLED_KEY = "enable-for-dart" 70 | 71 | ///默认值 72 | const val GENERATED_PATH_DEFAULT = "generated/json" 73 | 74 | data class PubSpecConfig( 75 | val project: Project, 76 | val pubRoot: PubRoot, 77 | val map: Map, 78 | //项目名称,导包需要 79 | val name: String = ((if (map[PROJECT_NAME] == "null") null else map[PROJECT_NAME]) ?: project.name).toString(), 80 | val flutterJsonMap: Map<*, *>? = map[PUBSPEC_KEY] as? Map<*, *>, 81 | val isFlutterModule: Boolean = FlutterModuleUtils.hasFlutterModule(project), 82 | // val isEnabled: Boolean = isOptionTrue(flutterJsonMap, PUBSPEC_ENABLE_PLUGIN_KEY), 83 | val generatedPath: String = optionString(flutterJsonMap, PUBSPEC_GENERATED_PATH_KEY, GENERATED_PATH_DEFAULT), 84 | ) 85 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/workers/FileGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.workers 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiDocumentManager 5 | import com.intellij.psi.PsiManager 6 | import com.github.zhangruiyu.flutterjsonbeanfactory.file.FileHelpers 7 | import com.github.zhangruiyu.flutterjsonbeanfactory.utils.YamlHelper 8 | 9 | /** 10 | * User: zhangruiyu 11 | * Date: 2019/12/22 12 | * Time: 15:34 13 | */ 14 | class FileGenerator(private val project: Project) { 15 | 16 | private var psiManager: PsiManager? = null 17 | private var documentManager: PsiDocumentManager? = null 18 | 19 | init { 20 | try { 21 | psiManager = PsiManager.getInstance(project) 22 | } catch (ignored: Throwable) { 23 | } 24 | try { 25 | documentManager = PsiDocumentManager.getInstance(project) 26 | } catch (ignored: Throwable) { 27 | } 28 | } 29 | 30 | fun generate() { 31 | val pubSpecConfig = YamlHelper.getPubSpecConfig(project) 32 | if (pubSpecConfig?.isFlutterModule == true) { 33 | psiManager?.let { psiManager -> 34 | documentManager?.let { documentManager -> 35 | val builder = StringBuilder() 36 | builder.append("// ignore_for_file: non_constant_identifier_names\n// ignore_for_file: camel_case_types\n// ignore_for_file: prefer_single_quotes\n\n") 37 | builder.append("// This file is automatically generated. DO NOT EDIT, all your changes would be lost.\n") 38 | // builder.append(JsonConvertContent) 39 | // builder.append("\n") 40 | builder.append(getJSONFieldContent()) 41 | val jsonConvertContent = builder.toString() 42 | FileHelpers.getJsonConvertJsonFiledFile(project,pubSpecConfig.generatedPath) { file -> 43 | psiManager.findFile(file)?.let { dartFile -> 44 | documentManager.getDocument(dartFile)?.let { document -> 45 | if (document.text != jsonConvertContent) { 46 | document.setText(jsonConvertContent) 47 | documentManager.commitDocument(document) 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | companion object { 58 | 59 | 60 | fun getJSONFieldContent(): String { 61 | return """ 62 | import 'package:meta/meta_meta.dart'; 63 | 64 | @Target({TargetKind.classType}) 65 | class JsonSerializable { 66 | const JsonSerializable(); 67 | } 68 | 69 | @Target({TargetKind.field}) 70 | class JSONField { 71 | //Specify the parse field name 72 | final String? name; 73 | 74 | //Whether to participate in toJson 75 | final bool? serialize; 76 | 77 | //Whether to participate in fromMap 78 | final bool? deserialize; 79 | 80 | //Whether to participate in copyWith 81 | final bool? copyWith; 82 | 83 | //Enumeration or not 84 | final bool? isEnum; 85 | 86 | const JSONField({this.name, this.serialize, this.deserialize, this.isEnum, this.copyWith}); 87 | } 88 | """ 89 | } 90 | 91 | } 92 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/workers/Initializer.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory.workers 2 | 3 | //import com.jetbrains.lang.dart.psi.* 4 | //import com.jetbrains.lang.dart.util.DartElementGenerator 5 | 6 | import com.intellij.openapi.editor.event.DocumentListener 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.openapi.startup.StartupActivity 9 | import com.intellij.psi.PsiDocumentManager 10 | import com.intellij.psi.PsiElement 11 | import java.util.regex.Pattern 12 | 13 | 14 | /** 15 | * User: zhangruiyu 16 | * Date: 2019/12/22 17 | * Time: 15:30 18 | */ 19 | /* 20 | class Initializer : StartupActivity, DocumentListener { 21 | private lateinit var documentManager: PsiDocumentManager 22 | 23 | override fun runActivity(project: Project) { 24 | App.project = project 25 | */ 26 | /*documentManager = PsiDocumentManager.getInstance(project) 27 | val connection: MessageBusConnection = project.messageBus.connect() 28 | 29 | connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener { 30 | override fun after(@NotNull events: List) { 31 | if (FileHelpers.shouldActivateFor(project)) { 32 | //如果改动文件包括了自动生成的entity.dart 33 | if (events.firstOrNull { 34 | it.path.endsWith("_entity.dart") 35 | } != null) { 36 | //那么此刻就去自动刷新 37 | project.executeCouldRollBackAction { 38 | FlutterBeanFactoryAction.generateAllFile(project) 39 | } 40 | } 41 | *//* 42 | */ 43 | /*for (event in events) { 44 | println("event = $event") 45 | }*//* 46 | */ 47 | /* 48 | } 49 | 50 | } 51 | })*//* 52 | 53 | */ 54 | /* 55 | Timer().scheduleAtFixedRate(0, 1000) { 56 | if (FileHelpers.shouldActivateFor(project)) { 57 | ApplicationManager.getApplication().invokeLater { 58 | runWriteAction { 59 | FileGenerator(project).generate() 60 | } 61 | } 62 | } 63 | }*//* 64 | 65 | 66 | } 67 | 68 | */ 69 | /* override fun documentChanged(event: DocumentEvent) { 70 | documentManager.commitDocument(event.document) 71 | val changedFile = documentManager.getPsiFile(event.document) as? JsonFile ?: return 72 | val undoManager = UndoManager.getInstance(changedFile.project) 73 | if (undoManager.isUndoInProgress || undoManager.isRedoInProgress) return 74 | 75 | if (PsiTreeUtil.findChildOfType(changedFile, PsiErrorElement::class.java) == null) { 76 | val lang = changedFile.virtualFile.nameWithoutExtension.substringAfter("_") 77 | val project = changedFile.project 78 | 79 | FileHelpers.getI18nFile(project) { dartFile -> 80 | @Suppress("NAME_SHADOWING") 81 | dartFile?.let { dartFile -> 82 | PsiManager.getInstance(project).findFile(dartFile)?.let { dartPsi -> 83 | PsiTreeUtil.findChildrenOfType(dartPsi, DartClass::class.java).firstOrNull { 84 | it.name == lang 85 | }?.let { replaceClass -> 86 | val methodItem = getMethodItem(event, changedFile, event.offset, replaceClass) 87 | methodItem.dartMethod?.delete() 88 | methodItem.method.isNullOrBlank().let { 89 | DartElementGenerator.createDummyFile(project, "class Dummy {\n${methodItem.method}}")?.let { dummyFile -> 90 | val methods = PsiTreeUtil.findChildrenOfAnyType( 91 | dummyFile, 92 | DartMethodDeclaration::class.java, 93 | DartGetterDeclaration::class.java 94 | ) 95 | 96 | methods.forEach { 97 | replaceClass.methods.last().parent.add(it) 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | private fun getMethodItem(event: DocumentEvent, file: JsonFile, offset: Int, clazz: DartClass): MethodItem { 109 | val property = PsiTreeUtil.findElementOfClassAtOffset(file, offset, JsonProperty::class.java, false) 110 | ?: return deleteMethod(event, file, clazz) 111 | 112 | val jsonValue = property.value 113 | val method: DartComponent? 114 | if (jsonValue == null) { 115 | PLURAL_MATCHER.reset(property.name) 116 | val find = PLURAL_MATCHER.find() 117 | method = when { 118 | find -> clazz.findMethodByName(PLURAL_MATCHER.group(1)) 119 | else -> clazz.findMethodByName(property.name) 120 | } 121 | return MethodItem(null, method) 122 | } 123 | 124 | 125 | val value = getStringFromExpression(jsonValue) 126 | val id = if (property.name.isBlank()) return MethodItem.empty else property.name 127 | 128 | val generator = I18nFileGenerator(file.project) 129 | 130 | 131 | PLURAL_MATCHER.reset(id) 132 | val find = PLURAL_MATCHER.find() 133 | 134 | val builder = StringBuilder() 135 | println(clazz.name) 136 | when { 137 | find -> { 138 | val methodName = PLURAL_MATCHER.group(1) 139 | val count = PLURAL_MATCHER.group(2) 140 | 141 | method = clazz.findMethodByName(methodName) 142 | when (method) { 143 | null -> generator.appendStringMethod(id, value, builder, shouldOverride(clazz)) 144 | is DartGetterDeclaration -> { 145 | PLURAL_MATCHER.reset(method.name) 146 | val isPlural = PLURAL_MATCHER.find() 147 | if (isPlural && !PLURAL_MATCHER.group(2).endsWith("other", true)) { 148 | val counts = arrayListOf(count) 149 | counts.add(PLURAL_MATCHER.group(2)) 150 | 151 | val map = hashMapOf(id to value) 152 | method.name?.let { methodName2 -> 153 | method.stringLiteralExpression?.let { literalExpression -> 154 | map[methodName2] = literalExpression.text.drop(1).dropLast(1) 155 | } 156 | } 157 | 158 | generator.appendPluralMethod(id, counts, map, builder, shouldOverride(clazz)) 159 | } else generator.appendStringMethod(id, value, builder, shouldOverride(clazz)) 160 | } 161 | is DartMethodDeclaration -> { 162 | val countsList = arrayListOf(count) 163 | val valuesMap = hashMapOf(id to value) 164 | 165 | PsiTreeUtil.findChildOfType(method, DartSwitchStatement::class.java)?.let { dartSwitch -> 166 | dartSwitch.switchCaseList.forEach { dartSwitchCase -> 167 | PsiTreeUtil.findChildOfType( 168 | dartSwitchCase.statements, 169 | DartStringLiteralExpression::class.java 170 | )?.let { expression -> 171 | val caseValue = getStringFromExpression(expression) 172 | val thisExpression = dartSwitchCase.expression ?: return@forEach 173 | val caseCount = generator.getCountFromValue(getStringFromExpression(thisExpression)) 174 | 175 | if (count != caseCount) { 176 | countsList.add(caseCount) 177 | valuesMap["$methodName$caseCount"] = caseValue 178 | } 179 | } 180 | } 181 | 182 | val dartSwitchCase = dartSwitch.defaultCase ?: return@let 183 | val defaultExpression = PsiTreeUtil.findChildOfType( 184 | dartSwitchCase.statements, 185 | DartStringLiteralExpression::class.java 186 | ) ?: return@let 187 | 188 | if (!count.equals("other", true)) { 189 | countsList.add("other") 190 | valuesMap["${methodName}other"] = getStringFromExpression(defaultExpression) 191 | } 192 | 193 | generator.appendPluralMethod(methodName, countsList, valuesMap, builder, shouldOverride(clazz)) 194 | } 195 | } 196 | } 197 | } 198 | value.contains("$") -> { 199 | method = clazz.findMethodByName(id) 200 | generator.appendParametrizedMethod(id, value, builder, shouldOverride(clazz)) 201 | } 202 | else -> { 203 | method = clazz.findMethodByName(id) 204 | generator.appendStringMethod(id, value, builder, shouldOverride(clazz)) 205 | } 206 | } 207 | 208 | return MethodItem(builder.toString(), method) 209 | } 210 | 211 | private fun deleteMethod(event: DocumentEvent, file: JsonFile, clazz: DartClass): MethodItem { 212 | println(event.oldFragment) 213 | if (event.oldFragment.isEmpty()) return MethodItem.empty 214 | 215 | val json = JsonElementGenerator(file.project).createDummyFile("{${event.oldFragment.trim()}}") 216 | 217 | val property = PsiTreeUtil.findChildOfType(json, JsonProperty::class.java) ?: return MethodItem.empty 218 | val id = property.name 219 | 220 | PLURAL_MATCHER.reset(id) 221 | val find = PLURAL_MATCHER.find() 222 | 223 | val generator = I18nFileGenerator(file.project) 224 | var method: DartComponent? 225 | val builder = StringBuilder() 226 | when { 227 | find -> { 228 | val methodName = PLURAL_MATCHER.group(1) 229 | val count = PLURAL_MATCHER.group(2) 230 | 231 | method = clazz.findMethodByName(methodName) 232 | when (method) { 233 | is DartGetterDeclaration -> method = clazz.findMethodByName(id) 234 | is DartMethodDeclaration -> { 235 | if (count.equals("other", true)) { 236 | PsiTreeUtil.findChildOfType(method, DartSwitchStatement::class.java)?.let { dartSwitch -> 237 | dartSwitch.switchCaseList.forEach { 238 | val expression = PsiTreeUtil.findChildOfType( 239 | it.statements, 240 | DartStringLiteralExpression::class.java 241 | ) ?: return@forEach 242 | val thisExpression = it.expression ?: return@forEach 243 | val caseCount = generator.getCountFromValue(getStringFromExpression(thisExpression)) ?: return@forEach 244 | 245 | generator.appendStringMethod( 246 | "$methodName$caseCount", 247 | getStringFromExpression(expression), 248 | builder, 249 | shouldOverride(clazz) 250 | ) 251 | } 252 | } 253 | } else { 254 | val countsList = arrayListOf() 255 | val valuesMap = hashMapOf() 256 | 257 | PsiTreeUtil.findChildOfType(method, DartSwitchStatement::class.java)?.let { dartSwitch -> 258 | dartSwitch.switchCaseList.forEach { dartSwitchCase -> 259 | val expression = PsiTreeUtil.findChildOfType( 260 | dartSwitchCase.statements, 261 | DartStringLiteralExpression::class.java 262 | ) ?: return@forEach 263 | val thisExpression = dartSwitchCase.expression ?: return@forEach 264 | val caseCount = generator.getCountFromValue(getStringFromExpression(thisExpression)) ?: return@forEach 265 | 266 | if ("$methodName$caseCount" != id) { 267 | countsList.add(caseCount) 268 | valuesMap["$methodName$caseCount"] = getStringFromExpression(expression) 269 | } 270 | } 271 | 272 | val dartSwitchCase = dartSwitch.defaultCase ?: return@let 273 | val defaultExpression = PsiTreeUtil.findChildOfType( 274 | dartSwitchCase.statements, 275 | DartStringLiteralExpression::class.java 276 | ) ?: return@let 277 | val defaultValue = getStringFromExpression(defaultExpression) 278 | countsList.add("other") 279 | valuesMap["${methodName}other"] = defaultValue 280 | 281 | generator.appendPluralMethod( 282 | methodName, 283 | countsList, 284 | valuesMap, 285 | builder, 286 | shouldOverride(clazz) 287 | ) 288 | } 289 | } 290 | } 291 | } 292 | } 293 | else -> method = clazz.findMethodByName(id) 294 | } 295 | 296 | return MethodItem(builder.toString(), method) 297 | } 298 | 299 | private fun shouldOverride(clazz: DartClass) = clazz.name != "en"*//* 300 | 301 | 302 | companion object { 303 | private val PLURAL_MATCHER = 304 | Pattern.compile("(.*)(zero|one|two|few|many|other)", Pattern.CASE_INSENSITIVE).matcher("") 305 | 306 | fun getStringFromExpression(expression: PsiElement?): String = expression?.text?.drop(1)?.dropLast(1) ?: "" 307 | } 308 | }*/ 309 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.ruiyu.ruiyu 4 | FlutterJsonBeanFactory 5 | 感谢大家,欢迎大家进入交流q群(963752388),接flutter外包,万分感谢 6 | 7 | Json to dart beans are provided, and dart files ending in entity are provided to generate dart bean factory for use.
9 |
right click on package -> `New`->`Dart bean clas file from JSON` And Then you will know how to use
10 |
If you change the fields in the class, just press the shortcut alt + j to regenerate the tojson and fromjson methods. The generated method regenerates all helper classes and JsonConvert classes (the same as the shortcut alt + j) each time an entity file is created in the generated/json directory.
11 |
If you need generic conversions in your network requests, use the jsonconvert.fromjsonast method directly.
12 |
If no helper files are generated, you can delete the .idea directory and restart your idea
13 | ]]>
14 | 16 | com.intellij.modules.json 17 | com.intellij.modules.lang 18 | com.intellij.modules.all 19 | 20 | com.intellij.modules.platform 21 | Dart 22 | org.jetbrains.kotlin 23 | io.flutter 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | F 15 | 16 | 17 | J 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/icons/action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/FlutterJsonBeanFactory/f25572e8ba9ba9158a0243c4aa2d67336b4dbbad/src/main/resources/icons/action.png -------------------------------------------------------------------------------- /src/main/resources/messages/MyBundle.properties: -------------------------------------------------------------------------------- 1 | name=My Plugin 2 | applicationService=Application service 3 | projectService=Project service: {0} 4 | -------------------------------------------------------------------------------- /src/test/kotlin/com/github/zhangruiyu/flutterjsonbeanfactory/MyPluginTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.zhangruiyu.flutterjsonbeanfactory 2 | 3 | import com.github.zhangruiyu.flutterjsonbeanfactory.action.dart_to_helper.node.GeneratorDartClassNodeToHelperInfo 4 | import com.intellij.ide.highlighter.XmlFileType 5 | import com.intellij.psi.xml.XmlFile 6 | import com.intellij.testFramework.TestDataPath 7 | import com.intellij.testFramework.fixtures.BasePlatformTestCase 8 | import com.intellij.util.PsiErrorElementUtil 9 | import com.jetbrains.lang.dart.DartFileType 10 | 11 | @TestDataPath("\$CONTENT_ROOT/src/test/testData") 12 | class MyPluginTest : BasePlatformTestCase() { 13 | 14 | fun testDartFile() { 15 | val psiFile = myFixture.configureByText( 16 | DartFileType.INSTANCE, """import 'package:testjson/generated/json/base/json_field.dart'; 17 | import 'dart:convert'; 18 | 19 | import 'package:testjson/generated/json/map_entity.g.dart'; 20 | 21 | @JsonSerializable() 22 | class MapEntity { 23 | 24 | late List codeTypesNull; 25 | 26 | MapEntity(); 27 | 28 | factory MapEntity.fromJson(Map json) => 29 | ${"\$"}MapEntityFromJson(json); 30 | 31 | Map toJson() => ${"\$"}MapEntityToJson(this); 32 | 33 | @override 34 | String toString() { 35 | return jsonEncode(this); 36 | } 37 | } 38 | 39 | """ 40 | ) 41 | GeneratorDartClassNodeToHelperInfo.getDartFileHelperClassGeneratorInfo(psiFile) 42 | println(psiFile.text) 43 | assertNotNull(psiFile.text.isNotEmpty()) 44 | } 45 | 46 | override fun getTestDataPath() = "src/test/testData/rename" 47 | 48 | fun testRename() { 49 | myFixture.testRename("foo.xml", "foo_after.xml", "a2") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/testData/rename/foo.xml: -------------------------------------------------------------------------------- 1 | 2 | 1>Foo 3 | 4 | -------------------------------------------------------------------------------- /src/test/testData/rename/foo_after.xml: -------------------------------------------------------------------------------- 1 | 2 | Foo 3 | 4 | -------------------------------------------------------------------------------- /test_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "glossary": { 3 | "title": "example glossary", 4 | "intList": [1,2,3,4], 5 | "GlossDiv": { 6 | "title": "S", 7 | "age": 1, 8 | "show": false, 9 | "GlossList": { 10 | "GlossEntry": { 11 | "ID": "SGML", 12 | "SortAs": "SGML", 13 | "GlossTerm": "Standard Generalized Markup Language", 14 | "Acronym": "SGML", 15 | "Abbrev": "ISO 8879:1986", 16 | "GlossDef": { 17 | "para": "A meta-markup language, used to create markup languages.", 18 | "GlossSeeAlso": [ 19 | "GML", 20 | "XML" 21 | ] 22 | }, 23 | "GlossSee": "markup" 24 | } 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /wechat_pay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/FlutterJsonBeanFactory/f25572e8ba9ba9158a0243c4aa2d67336b4dbbad/wechat_pay.png --------------------------------------------------------------------------------