├── .buildscript ├── deploy_snapshot.sh └── update_docs.sh ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── RELEASING.md ├── build.gradle ├── compiler-extensions ├── inspector-android-compiler-extension │ ├── README.md │ ├── build.gradle │ ├── gradle.properties │ └── src │ │ └── main │ │ └── java │ │ └── io │ │ └── sweers │ │ └── inspector │ │ └── extensions │ │ └── android │ │ ├── AndroidInspectorExtension.java │ │ └── package-info.java ├── inspector-autovalue-compiler-extension │ ├── README.md │ ├── build.gradle │ ├── gradle.properties │ └── src │ │ └── main │ │ └── java │ │ └── io │ │ └── sweers │ │ └── inspector │ │ └── extensions │ │ └── autovalue │ │ ├── AutoValueInspectorExtension.java │ │ └── package-info.java ├── inspector-nullability-compiler-extension │ ├── README.md │ ├── build.gradle │ ├── gradle.properties │ └── src │ │ └── main │ │ └── java │ │ └── io │ │ └── sweers │ │ └── inspector │ │ └── extensions │ │ └── nullability │ │ ├── NullabilityInspectorExtension.java │ │ └── package-info.java └── inspector-rave-compiler-extension │ ├── README.md │ ├── build.gradle │ ├── gradle.properties │ └── src │ └── main │ └── java │ └── io │ └── sweers │ └── inspector │ └── extensions │ └── rave │ ├── RaveInspectorExtension.java │ └── package-info.java ├── gradle.properties ├── gradle ├── dependencies.gradle ├── gradle-mvn-push.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── inspector-compiler-annotations ├── README.md ├── build.gradle ├── gradle.properties └── src │ └── main │ └── java │ └── io │ └── sweers │ └── inspector │ └── compiler │ └── annotations │ ├── GenerateValidator.java │ └── package-info.java ├── inspector-compiler-extensions-api ├── README.md ├── build.gradle ├── gradle.properties └── src │ └── main │ └── java │ └── io │ └── sweers │ └── inspector │ └── compiler │ └── plugins │ └── spi │ ├── InspectorExtension.java │ ├── Property.java │ └── package-info.java ├── inspector-compiler ├── README.md ├── build.gradle ├── gradle.properties └── src │ ├── main │ └── java │ │ └── io │ │ └── sweers │ │ └── inspector │ │ └── compiler │ │ ├── InspectorProcessor.java │ │ └── package-info.java │ └── test │ ├── java │ └── io │ │ └── sweers │ │ └── inspector │ │ └── compiler │ │ └── InspectorProcessorTest.java │ └── resources │ ├── DateValidator.java │ ├── Person.java │ └── Validator_Person.java ├── inspector-factory-compiler-annotations ├── README.md ├── build.gradle ├── gradle.properties └── src │ └── main │ └── java │ └── io │ └── sweers │ └── inspector │ └── factorycompiler │ ├── InspectorFactory.java │ └── package-info.java ├── inspector-factory-compiler ├── README.md ├── build.gradle ├── gradle.properties └── src │ ├── main │ └── java │ │ └── io │ │ └── sweers │ │ └── inspector │ │ └── factorycompiler │ │ ├── InspectorFactoryProcessor.java │ │ └── package-info.java │ └── test │ ├── java │ └── io │ │ └── sweers │ │ └── inspector │ │ └── factorycompiler │ │ └── InspectorFactoryProcessorTest.java │ └── resources │ ├── InspectorFactory_MyFactory.java │ ├── MyFactory.java │ ├── Person.java │ ├── PersonFive.java │ ├── PersonFour.java │ ├── PersonThree.java │ └── PersonTwo.java ├── inspector-retrofit ├── README.md ├── build.gradle ├── gradle.properties └── src │ ├── main │ └── java │ │ └── io │ │ └── sweers │ │ └── inspector │ │ └── retrofit │ │ ├── InspectorConverterFactory.java │ │ └── package-info.java │ └── test │ └── java │ └── io │ └── sweers │ └── inspector │ └── retrofit │ └── InspectorConverterFactoryTest.java ├── inspector-sample-android ├── README.md ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── sweers │ └── inspector │ └── android │ ├── IntRangeValidator.java │ └── package-info.java ├── inspector-sample ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── sweers │ │ └── inspector │ │ └── sample │ │ ├── DateValidator.java │ │ ├── Person.java │ │ ├── RetrofitSample.java │ │ ├── SampleFactory.java │ │ ├── SecondaryDateValidator.java │ │ ├── SelfValidatingPerson.java │ │ └── package-info.java │ └── test │ └── java │ └── io │ └── sweers │ └── inspector │ └── sample │ └── integrationtest │ └── IntegrationTest.java ├── inspector ├── build.gradle ├── gradle.properties └── src │ ├── main │ └── java │ │ └── io │ │ └── sweers │ │ └── inspector │ │ ├── ArrayValidator.java │ │ ├── ClassFactory.java │ │ ├── ClassValidator.java │ │ ├── CollectionValidator.java │ │ ├── CompositeValidationException.java │ │ ├── CompositeValidator.java │ │ ├── Inspector.java │ │ ├── InspectorIgnored.java │ │ ├── MapValidator.java │ │ ├── SelfValidating.java │ │ ├── StandardValidators.java │ │ ├── Types.java │ │ ├── Util.java │ │ ├── Validate.java │ │ ├── ValidatedBy.java │ │ ├── ValidationException.java │ │ ├── ValidationQualifier.java │ │ ├── Validator.java │ │ └── package-info.java │ └── test │ └── java │ └── io │ └── sweers │ └── inspector │ ├── CompositeValidatorTest.java │ └── InspectorTest.java └── settings.gradle /.buildscript/deploy_snapshot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo. 4 | # 5 | # Adapted from https://coderwall.com/p/9b_lfq and 6 | # http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ 7 | 8 | SLUG="hzsweers/inspector" 9 | JDK="oraclejdk8" 10 | BRANCH="master" 11 | 12 | set -e 13 | 14 | if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then 15 | echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'." 16 | elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then 17 | echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'." 18 | elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then 19 | echo "Skipping snapshot deployment: was pull request." 20 | elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then 21 | echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'." 22 | else 23 | echo "Deploying snapshot..." 24 | ./gradlew clean uploadArchives 25 | echo "Snapshot deployed!" 26 | fi 27 | -------------------------------------------------------------------------------- /.buildscript/update_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Cloning osstrich..." 4 | mkdir tmp 5 | cd tmp 6 | git clone git@github.com:square/osstrich.git 7 | cd osstrich 8 | echo "Packaging..." 9 | mvn package 10 | echo "Running..." 11 | rm -rf tmp/inspector && java -jar target/osstrich-cli.jar tmp/inspector git@github.com:hzsweers/inspector.git io.sweers.inspector 12 | echo "Cleaning up..." 13 | cd ../.. 14 | rm -rf tmp 15 | echo "Finished!" 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###OSX### 2 | 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Icon must ends with two \r. 8 | Icon 9 | 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear on external disk 15 | .Spotlight-V100 16 | .Trashes 17 | 18 | 19 | ###Linux### 20 | 21 | *~ 22 | 23 | # KDE directory preferences 24 | .directory 25 | 26 | 27 | ###Android### 28 | 29 | # Built application files 30 | *.apk 31 | *.ap_ 32 | 33 | # Files for ART and Dalvik VM 34 | *.dex 35 | 36 | # Java class files 37 | *.class 38 | 39 | # Generated files 40 | bin/ 41 | gen/ 42 | 43 | # Gradle files 44 | .gradle/ 45 | .gradletasknamecache 46 | build/ 47 | 48 | # Local configuration file (sdk path, etc) 49 | local.properties 50 | 51 | # Proguard folder generated by Eclipse 52 | proguard/ 53 | 54 | # Lint 55 | lint-report.html 56 | lint-report_files/ 57 | lint_result.txt 58 | 59 | # Mobile Tools for Java (J2ME) 60 | .mtj.tmp/ 61 | 62 | # Package Files # 63 | *.war 64 | *.ear 65 | 66 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 67 | hs_err_pid* 68 | 69 | 70 | ###IntelliJ### 71 | 72 | *.iml 73 | *.ipr 74 | *.iws 75 | .idea/ 76 | 77 | 78 | ###Eclipse### 79 | 80 | *.pydevproject 81 | .metadata 82 | tmp/ 83 | *.tmp 84 | *.bak 85 | *.swp 86 | *~.nib 87 | .settings/ 88 | .loadpath 89 | 90 | # External tool builders 91 | .externalToolBuilders/ 92 | 93 | # Locally stored "Eclipse launch configurations" 94 | *.launch 95 | 96 | # CDT-specific 97 | .cproject 98 | 99 | # PDT-specific 100 | .buildpath 101 | 102 | # sbteclipse plugin 103 | .target 104 | 105 | # TeXlipse plugin 106 | .texlipse 107 | 108 | # kotlin 109 | out/ 110 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | install: true 7 | 8 | script: 9 | - ./gradlew clean build --stacktrace 10 | 11 | after_success: 12 | - .buildscript/deploy_snapshot.sh 13 | 14 | env: 15 | global: 16 | - secure: "vsDKIPnoWy+59UyPIrc7TqY+CwfCUcjFDiYSd2TJMmxR+Q9gzckhwY42Bl4igUq78euY9lnpawYkUdZ3pX/0ic4Dg/VLl4Fj7mEwnPShRkN1vJePiKEYnamFUT30RNW9EpZmFWgWkwFWMI02ZhT99y3vADbT4czwUqDRdHnSTTvGUO+aovqFpCgu9zmw11XxKmsuujsrVeACHkmJ9cEHc0HhXLQZDBa4Qer8uYFRHfAg7+la8NSVYdU6fJEXOkU33RP83JnmxvQGnEwQ3YjZz9Ljw1AJPUzex6WfuIeeMzRm4MAwRTS5fqXWlwLjY3wqywiKx2VgsBlMiBFkvMYEavSF3jmD7ggcDY/L/xisVUM27liTjXEbc3ig1vZH0W4fAnnAr/JetHLMOOyE342IRPg7k2Ycj4b2zfbOuimXT0znwj9cA9Ygi4lJc7gAEio+4qWbE7UxzJYALogmqn51rz+xMjZPDqQ9KKLE5PGK0+iLFNig4URz79+BEkuJ3IYHnswbGZVf/A4nOuOrHKSt2SGjvbNI21/Iq6XwRaHhV2UiE1xmCMdpy6htfNJX2oTwcBlsjU/ZoTY0RP8Gij4W7+KHHqSLnr9OJPPN1CFiQujASyuEKe5bBBWgEasYN5dx4Rt0WRqm6E7wqKO/R9Ng5Ge8AcHcE+ShaTTYEn6eUiE=" 17 | - secure: "LTYvjX491AFRXeiA0x7Y3IGYw0hUG5sGoFTkNVN2/WncyAMaw6d8kL7nSQ9lT4s393PoodKVTL5nO3MMYX/JSH8VZ+wBU9MrYVAoonRudt3A0XBGyYy4gGPHy7lHaNg4gs1ksxVXROUI06QCfVmBEHIWXvbEu9xjodWg25YIRcPmTQ3tuApWsG331+vRgEHbAHtQfqv5GxOknD0pgvb6S0qqTe/A34cue3GIRK/qXyJ6MUkTaTJjIbZqu4v2iOOUQ8CKsBeTqIQ69UxM1RQnCiXpOH9Rl725kzwRsxfEqIz0n45hgCZElY2mm2rpJJ9Q+KORRkTLyz6JZ1Axpb1AVw2ArAFZi+gwOJujV25JkUjZ7U6OT3Thbrv3T1SwvH34R67mfjcAHbL8TSq9lJvKpdKcx8RjnGqmhgC32sQAqWRaZLMM06uTAnjb5+j6YmAp1kpLX9+dboSB5qC9PZGokfQWlEJc8hdkf+mO3s2grYZ9gvojWrHvgM7ej6uAI6QH1NjCknwC4HPuRkaxd4GEBSWz8NVcrCTKAjyeSm9UryRhIfr1mznLdRzJlAW1HF1tvZ4+oIsgOgQxt8pdjWNYoY+c7dOQ8gIcCG/7BAKR5umDmcUZz39s8HkMMVHfj/9yBHix+pN9cYgBB9K+xabKxwW02M/jj/LTlb7PO99f2bk=" 18 | 19 | branches: 20 | except: 21 | - gh-pages 22 | 23 | notifications: 24 | email: false 25 | 26 | cache: 27 | directories: 28 | - $HOME/.gradle 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | Version 0.3.0 5 | ------------------------- 6 | 7 | _2018-2-12_ 8 | 9 | This is a rather huge release with a lot of updates. It shouldn't be anything compile-breaking though 10 | hopefully! 11 | 12 | * Composite validation support via `CompositeValidator` and allowing multiple validators in `@ValidatedBy`. 13 | * Fleshed out sample with more examples 14 | * Generics support 15 | * Composite validation 16 | * Retrofit example. Drop this in to your retrofit instance to validate server responses! 17 | * A *lot* of generics improvements 18 | * Prior to this release, reflective generics were unfortunately broken 19 | * Support for self validation. If a type implements `SelfValidating`, the reflective validator factory 20 | and compiler will just defer to their validation logic. 21 | * The factory compiler will ignore these types completely. 22 | * AutoValue compiler extension no longer has a hard transitive dependency on AutoValue. 23 | * New artifact (`inspector-compiler-annotations`) with a `@GenerateValidator` annotation. Use this 24 | annotation to indicate to the compiler that you want it to generate the validator regardless of whether 25 | or not there's a static `Validator`-returning method. 26 | * This comes with an optional `GenerateValidator.FACTORY` Validator factory you can use to do runtime 27 | lookups of the generated Validators. This can be a good alternative solution in multimodule projects, 28 | where registering multiple factories to upstream Inspector instances can be problematic. 29 | * The factory compiler supports 0-arg static `Validator`-returning methods, for types that bring their 30 | own validation. 31 | 32 | Version 0.2.0 33 | ------------------------- 34 | 35 | _2017-7-17_ 36 | 37 | * New: Separate `inspector-factory-compiler-annotations` artifact for better android project compatibility (#11) 38 | * Fix: AutoValue artifact should show up correctly now (#9) 39 | * Fix: Use `t` for the type parameter in `Validator#validate()` so the IDE better autocompletes the name based on the actual type (#12) 40 | 41 | Version 0.1.1 42 | ------------------------- 43 | 44 | _2017-7-17_ 45 | 46 | * New: Lowered requirements around validator methods when generating factories (#7) 47 | * New: Changed parameter names from `object` to `validationTarget` where possible for nicer kotlin autocompletes (#8) 48 | 49 | Version 0.1.0 50 | ------------------------- 51 | 52 | _2017-7-16_ 53 | 54 | * Initial release 55 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Inspector 2 | =========== 3 | 4 | [![Build Status](https://travis-ci.org/hzsweers/inspector.svg?branch=master)](https://travis-ci.org/hzsweers/inspector) 5 | 6 | Inspector is a tiny class validation tool that has a tiny, flexible, and powerful API. 7 | 8 | If you've ever used Moshi or GSON, it should feel very familiar. 9 | 10 | The base type is `Inspector`, which can contain any number of `Validator`s or `Validator.Factory`s. 11 | 12 | Like Moshi, Inspector operates on `Type`s directly. Validation is a decentralized, pluggable system. 13 | There is a reflective class-based validator generator if you want, or you can use the `inspector-compiler` 14 | annotation processor to generate validator implementations for you. 15 | 16 | Validation normally operates on method return types. Everything is assumed non-null, unless annotated 17 | with one of the number of `@Nullable` annotations out there. Nullability is the only validation run 18 | out of the box by the reflective adapter as well. Inspector is purposefully stupid, you know your own models! 19 | 20 | Usage looks like this: 21 | 22 | ```java 23 | Inspector inspector = new Inspector.Builder() 24 | .add(Foo.class, new FooValidator()) 25 | .add(BarValidator.FACTORY) 26 | .add(...) // Whatever you want! 27 | .build(); 28 | 29 | boolean isValid = inspector.validator(Foo.class).isValid(myFooStance); 30 | 31 | // Or more aggressively, but with descriptive error messages 32 | try { 33 | inspector.validator(Foo.class).validate(myFooInstance); 34 | } catch (ValidationException e) { 35 | // Server's sending you bad stuff 36 | } 37 | ``` 38 | 39 | Note that you have two ways of checking validation: the non-throwing `isValid()` method for quick 40 | boolean checks, or the throwing `validate()` method that throws an exception with potentially more 41 | information. 42 | 43 | ### Annotations 44 | 45 | `@ValidationQualifier` - Like Moshi's `@JsonQualifier` or javax's `@Qualifer`. Use these on your own 46 | annotations to create custom validators with interesting implementations. 47 | 48 | `@InspectorExcluded` - Use this to exclude methods from validation. 49 | 50 | `@ValidatedBy(SomeValidator.class)` - Use this to designate that this should be validated by 51 | a specified validator. Note that if you use this reflectively, it must also be reflectively instantiable. 52 | 53 | ### inspector-compiler 54 | 55 | Features: 56 | - Annotation processor that generates validator implementations 57 | - Supports a service-loader-based extensions API via `InspectorExtension` 58 | - First party implementations are under the `compiler-extensions` directory 59 | - Validator implementation is generated in the same package in a `Validator_.java` 60 | 61 | Simply add a static `Validator`-returning method to your model and be sure to annotate it with 62 | something to look for, such as `@AutoValue` (this can be done automatically via the `inspector-autovalue-compiler-extension`). 63 | 64 | First party extensions: 65 | - `inspector-android-compiler-extension`: Generates validation for Android support annotations 66 | - `inspector-autovalue-compiler-extension`: Tells the inspector compiler to look for `@AutoValue` annotations 67 | - `inspector-nullability-compiler-extension`: Generates nullability validation based on the presence of `@Nullable` annotations 68 | - `inspector-rave-compiler-extension`: Generates validation for RAVE annotations 69 | 70 | ```java 71 | @AutoValue 72 | public abstract class Foo { 73 | 74 | public abstract String bar(); 75 | 76 | public static Validator validator(Inspector inspector) { 77 | // Generated class is in the same package, prefixed with "Validator_" 78 | return new Validator_Foo(insepctor); 79 | } 80 | } 81 | ``` 82 | 83 | If you have a lot of these, you may not want to manually have to hook all these up to an inspector instance. To solve 84 | this, there are two batteries-included options you can use. 85 | 86 | The `inspector-factory-compiler` annotation processor can generate a factory implementation that 87 | delegates to all the types on that compilation path. Simply stub a factory class like so: 88 | 89 | ```java 90 | @InspectorFactory(include = AutoValue.class) 91 | public abstract class MyFactory implements Validator.Factory { 92 | public static MyFactory create() { 93 | return new InspectorFactory_MyFactory(); 94 | } 95 | } 96 | ``` 97 | 98 | - Your class must implement ValidatorFactory and not be `final`. 99 | - You mark which annotations you want to target your factory to, such as `@AutoValue`. 100 | - An `InspectorFactory_` will be generated in the same package for referencing with an implementation 101 | of the `create()` method. 102 | 103 | The other option is to use `@GenerateValidator`. Simply annotate desired types with this, and a `Validator` 104 | implementation will be generated (regardless of the presence of a static `Validator`-returning method). You 105 | can read these via optional `Validator.Factory` implementation in the annotation via `GenerateValidator.FACTORY`. 106 | 107 | ```java 108 | @GenerateValidator 109 | class Foo { 110 | public String bar() { 111 | //... 112 | } 113 | } 114 | 115 | // Later 116 | Inspector inspector = new Inspector.Builder() 117 | .add(GenerateValidator.FACTORY) 118 | .build(); 119 | 120 | inspector.validator(Foo.class).validate(fooInstance); // Validator_Foo.java will be dynamically looked up! 121 | ``` 122 | 123 | ### Tools 124 | 125 | There's some helpful tools available: 126 | * `CompositeValidator` for composing multiple `Validator`s for a given type/property. 127 | * `Types` is a utility class with helpful factories for creating different `Type` implementations. 128 | * Types can implement `SelfValidating` to indicate that they handle their own validation, and thus Inspector will just defer to that. 129 | * There is an `inspector-retrofit` artifact with a [Retrofit][retrofit] `Converter.Factory` 130 | implementation that you can drop in to your network stack. 131 | 132 | ### Sample 133 | 134 | There is a sample project under `inspector-sample` with nontrivial examples. 135 | 136 | `inspector-sample-android` is just a for-fun proof of concept of how some non-generated support library annotation validators 137 | would look. This unfortunately is not currently possible since support annotations are not `RUNTIME` retained. 138 | 139 | ### Usage 140 | 141 | Your gradle file could look like this: 142 | 143 | [![Maven Central](https://img.shields.io/maven-central/v/io.sweers.inspector/inspector.svg)](https://mvnrepository.com/artifact/io.sweers.inspector/inspector) 144 | 145 | ```gradle 146 | depedencies { 147 | // Core Inspector library 148 | implementation 'io.sweers.inspector:inspector:x.y.z' 149 | 150 | // Compiler to generate validators, in a Java/Android project 151 | annotationProcessor 'io.sweers.inspector:inspector-compiler:x.y.z' 152 | 153 | // Or Kotlin (± Android) 154 | kapt 'io.sweers.inspector:inspector-compiler:x.y.z' 155 | 156 | // Compiler annotations 157 | implementation 'io.sweers.inspector:inspector-compiler-annotations:x.y.z' 158 | 159 | // Retrofit artifact 160 | implementation 'io.sweers.inspector:inspector-retrofit:x.y.z' 161 | 162 | // Optional compiler extensions 163 | compileOnly 'io.sweers.inspector:inspector-android-compiler-extension:x.y.z' 164 | compileOnly 'io.sweers.inspector:inspector-autovalue-compiler-extension:x.y.z' 165 | compileOnly 'io.sweers.inspector:inspector-nullability-compiler-extension:x.y.z' 166 | compileOnly 'io.sweers.inspector:inspector-rave-compiler-extension:x.y.z' 167 | } 168 | ``` 169 | 170 | Snapshots of the development version are available in [Sonatype's snapshots repository][snapshots]. 171 | 172 | ### Notes 173 | 174 | See subprojects for more detailed READMEs about specific artifacts. Full Javadocs can be found at https://hzsweers.github.io/inspector/0.x/ 175 | 176 | This library is heavily influenced by Moshi and RAVE. 177 | 178 | License 179 | ------- 180 | 181 | Copyright (C) 2017 Zac Sweers 182 | 183 | Licensed under the Apache License, Version 2.0 (the "License"); 184 | you may not use this file except in compliance with the License. 185 | You may obtain a copy of the License at 186 | 187 | http://www.apache.org/licenses/LICENSE-2.0 188 | 189 | Unless required by applicable law or agreed to in writing, software 190 | distributed under the License is distributed on an "AS IS" BASIS, 191 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 192 | See the License for the specific language governing permissions and 193 | limitations under the License. 194 | 195 | [retrofit]: https://github.com/square/retrofit 196 | [snapshots]: https://oss.sonatype.org/content/repositories/snapshots/ 197 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | Releasing 2 | ========= 3 | 4 | 1. Change the version in `gradle.properties` to a non-SNAPSHOT version. 5 | 2. Update the `CHANGELOG.md` for the impending release. 6 | 3. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version) 7 | 4. `git tag -a X.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version) 8 | 5. `./gradlew clean uploadArchives -Dorg.gradle.parallel=false` 9 | 6. Update the `gradle.properties` to the next SNAPSHOT version. 10 | 7. `git commit -am "Prepare next development version."` 11 | 8. `git push && git push --tags` 12 | 9. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact. 13 | - Select the artifact, click `close`, wait for it to close, then select again and click 14 | `release`. 15 | 10. After release propagates (wait ~1 hour), update Javadocs via [Osstrich](https://github.com/square/osstrich) 16 | - Make sure you have push access 17 | - `./.buildscript/update_docs.sh` 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | } 6 | } 7 | 8 | repositories { 9 | jcenter() 10 | google() 11 | } 12 | } 13 | 14 | task wrapper(type: Wrapper) { 15 | gradleVersion = '4.5.1' 16 | distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" 17 | } 18 | 19 | task clean(type: Delete) { 20 | delete rootProject.buildDir 21 | } 22 | 23 | apply from: 'gradle/dependencies.gradle' 24 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-android-compiler-extension/README.md: -------------------------------------------------------------------------------- 1 | inspector-android-compiler-extension 2 | ==================================== 3 | 4 | An inspector compiler extension that generates validation for Android support library annotations. 5 | 6 | Supported annotations: 7 | - FloatRange 8 | - IntDef 9 | - IntRange 10 | - Size 11 | - StringDef 12 | 13 | Note that nullability annotations are not supported here. Use the `inspector-nullability-compiler-extension` instead. 14 | 15 | [![Maven Central](https://img.shields.io/maven-central/v/io.sweers.inspector/inspector-android-compiler-extension.svg)](https://mvnrepository.com/artifact/io.sweers.inspector/inspector-android-compiler-extension) 16 | ```gradle 17 | compileOnly 'io.sweers.inspector:inspector-android-compiler-extension:x.y.z' 18 | ``` 19 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-android-compiler-extension/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.errorprone' 20 | } 21 | 22 | sourceCompatibility = JavaVersion.VERSION_1_8 23 | targetCompatibility = JavaVersion.VERSION_1_8 24 | 25 | repositories { 26 | google() 27 | } 28 | 29 | dependencies { 30 | api project(':inspector-compiler-extensions-api') 31 | api deps.misc.guava 32 | api deps.misc.javapoet 33 | api deps.support.annotations 34 | compileOnly deps.misc.javaxExtras 35 | compileOnly deps.auto.service 36 | compileOnly deps.misc.errorProneAnnotations 37 | 38 | errorprone deps.build.errorProne 39 | } 40 | 41 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 42 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-android-compiler-extension/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Android Inspector Extension 2 | POM_ARTIFACT_ID=inspector-android-compiler-extension 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-android-compiler-extension/src/main/java/io/sweers/inspector/extensions/android/AndroidInspectorExtension.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.extensions.android; 2 | 3 | import android.support.annotation.FloatRange; 4 | import android.support.annotation.IntDef; 5 | import android.support.annotation.IntRange; 6 | import android.support.annotation.LongDef; 7 | import android.support.annotation.Size; 8 | import android.support.annotation.StringDef; 9 | import com.google.auto.service.AutoService; 10 | import com.google.common.collect.Sets; 11 | import com.google.common.primitives.Ints; 12 | import com.google.common.primitives.Longs; 13 | import com.squareup.javapoet.ArrayTypeName; 14 | import com.squareup.javapoet.CodeBlock; 15 | import com.squareup.javapoet.ParameterSpec; 16 | import com.squareup.javapoet.ParameterizedTypeName; 17 | import io.sweers.inspector.ValidationException; 18 | import io.sweers.inspector.compiler.plugins.spi.InspectorExtension; 19 | import io.sweers.inspector.compiler.plugins.spi.Property; 20 | import java.lang.annotation.Annotation; 21 | import java.util.Arrays; 22 | import java.util.Collection; 23 | import java.util.Set; 24 | import java.util.stream.Collectors; 25 | import javax.annotation.Nullable; 26 | import javax.lang.model.element.AnnotationMirror; 27 | 28 | @AutoService(InspectorExtension.class) public final class AndroidInspectorExtension 29 | implements InspectorExtension { 30 | 31 | private static final Set> SUPPORTED_ANNOTATIONS = 32 | Sets.newLinkedHashSet(Arrays.asList(FloatRange.class, IntRange.class, Size.class)); 33 | 34 | private static final Set> SUPPORTED_ANNOTATIONS_OF_ANNOTATIONS = 35 | Sets.newLinkedHashSet(Arrays.asList(IntDef.class, LongDef.class, StringDef.class)); 36 | 37 | @Override public boolean applicable(Property property) { 38 | for (Class a : SUPPORTED_ANNOTATIONS) { 39 | if (property.element.getAnnotation(a) != null) { 40 | return true; 41 | } 42 | } 43 | for (Class a : SUPPORTED_ANNOTATIONS_OF_ANNOTATIONS) { 44 | if (findAnnotationByAnnotation(property.element.getAnnotationMirrors(), a) != null) { 45 | return true; 46 | } 47 | } 48 | return false; 49 | } 50 | 51 | @Override 52 | public CodeBlock generateValidation(Property prop, String variableName, ParameterSpec value) { 53 | return addAndroidChecks(prop, variableName); 54 | } 55 | 56 | @Override public String toString() { 57 | return AndroidInspectorExtension.class.getSimpleName(); 58 | } 59 | 60 | private static CodeBlock addAndroidChecks(Property prop, String variableName) { 61 | CodeBlock.Builder validationBlock = CodeBlock.builder(); 62 | IntRange intRange = prop.annotation(IntRange.class); 63 | if (intRange != null) { 64 | long from = intRange.from(); 65 | long to = intRange.to(); 66 | if (from != Long.MIN_VALUE) { 67 | validationBlock.beginControlFlow("if ($L < $L)", variableName, from) 68 | .addStatement("throw new $T(\"$L must be greater than $L but is \" + $L)", 69 | ValidationException.class, 70 | prop.methodName, 71 | from, 72 | variableName) 73 | .endControlFlow(); 74 | } 75 | if (to != Long.MAX_VALUE) { 76 | validationBlock.beginControlFlow("else if ($L > $L)", variableName, to) 77 | .addStatement("throw new $T(\"$L must be less than $L but is \" + $L)", 78 | ValidationException.class, 79 | prop.methodName, 80 | to, 81 | variableName) 82 | .endControlFlow(); 83 | } 84 | } 85 | FloatRange floatRange = prop.annotation(FloatRange.class); 86 | if (floatRange != null) { 87 | double from = floatRange.from(); 88 | double to = floatRange.to(); 89 | if (from != Double.NEGATIVE_INFINITY) { 90 | validationBlock.beginControlFlow("if ($L < $L)", variableName, from) 91 | .addStatement("throw new $T(\"$L must be greater than $L but is \" + $L)", 92 | ValidationException.class, 93 | prop.methodName, 94 | from, 95 | variableName) 96 | .endControlFlow(); 97 | } 98 | if (to != Double.POSITIVE_INFINITY) { 99 | validationBlock.beginControlFlow("else if ($L > $L)", variableName, to) 100 | .addStatement("throw new $T(\"$L must be less than $L but is \" + $L)", 101 | ValidationException.class, 102 | prop.methodName, 103 | to, 104 | variableName) 105 | .endControlFlow(); 106 | } 107 | } 108 | Size size = prop.annotation(Size.class); 109 | if (size != null) { 110 | String sizeVar = variableName + "Size"; 111 | if (prop.type instanceof ArrayTypeName) { 112 | validationBlock.addStatement("int $L = $L.length", sizeVar, variableName); 113 | } else if (prop.type instanceof ParameterizedTypeName) { 114 | // Assume it's a collection or map 115 | validationBlock.addStatement("int $L = $L.size()", sizeVar, variableName); 116 | } 117 | long exact = size.value(); 118 | long min = size.min(); 119 | long max = size.max(); 120 | long multiple = size.multiple(); 121 | if (exact != -1) { 122 | validationBlock.beginControlFlow("if ($L != $L)", sizeVar, exact) 123 | .addStatement("throw new $T(\"$L's size must be exactly $L but is \" + $L)", 124 | ValidationException.class, 125 | prop.methodName, 126 | exact, 127 | sizeVar) 128 | .endControlFlow(); 129 | } 130 | if (min != Long.MIN_VALUE) { 131 | validationBlock.beginControlFlow("if ($L < $L)", sizeVar, min) 132 | .addStatement("throw new $T(\"$L's size must be greater than $L but is \" + $L)", 133 | ValidationException.class, 134 | prop.methodName, 135 | min, 136 | sizeVar) 137 | .endControlFlow(); 138 | } 139 | if (max != Long.MAX_VALUE) { 140 | validationBlock.beginControlFlow("if ($L > $L)", sizeVar, max) 141 | .addStatement("throw new $T(\"$L's size must be less than $L but is \" + $L)", 142 | ValidationException.class, 143 | prop.methodName, 144 | max, 145 | sizeVar) 146 | .endControlFlow(); 147 | } 148 | if (multiple != 1) { 149 | validationBlock.beginControlFlow("if ($L % $L != 0)", sizeVar, multiple) 150 | .addStatement("throw new $T(\"$L's size must be a multiple of $L but is \" + $L)", 151 | ValidationException.class, 152 | prop.methodName, 153 | multiple, 154 | sizeVar) 155 | .endControlFlow(); 156 | } 157 | } 158 | 159 | IntDef intDef = findAnnotationByAnnotation(prop.element.getAnnotationMirrors(), IntDef.class); 160 | if (intDef != null) { 161 | int[] values = intDef.value(); 162 | validationBlock.beginControlFlow("if (!($L))", 163 | String.join(" && ", 164 | Ints.asList(values) 165 | .stream() 166 | .map(l -> variableName + " != " + l) 167 | .collect(Collectors.toList()))) 168 | .addStatement("throw new $T(\"$L's value must be within scope of its IntDef. Is \" + $L)", 169 | ValidationException.class, 170 | prop.methodName, 171 | variableName) 172 | .endControlFlow(); 173 | } 174 | 175 | LongDef longDef = findAnnotationByAnnotation(prop.element.getAnnotationMirrors(), LongDef.class); 176 | if (longDef != null) { 177 | long[] values = longDef.value(); 178 | validationBlock.beginControlFlow("if (!($L))", 179 | String.join(" && ", 180 | Longs.asList(values) 181 | .stream() 182 | .map(l -> variableName + " != " + l + "L") 183 | .collect(Collectors.toList()))) 184 | .addStatement("throw new $T(\"$L's value must be within scope of its LongDef. Is \" + $L)", 185 | ValidationException.class, 186 | prop.methodName, 187 | variableName) 188 | .endControlFlow(); 189 | } 190 | StringDef stringDef = 191 | findAnnotationByAnnotation(prop.element.getAnnotationMirrors(), StringDef.class); 192 | if (stringDef != null) { 193 | String[] values = stringDef.value(); 194 | validationBlock.beginControlFlow("if (!($L))", 195 | String.join(" && ", 196 | Arrays.stream(values) 197 | .map(s -> "\"" + s + "\".equals(" + variableName + ")") 198 | .collect(Collectors.toList()))) 199 | .addStatement( 200 | "throw new $T(\"$L's value must be within scope of its StringDef. Is \" + $L)", 201 | ValidationException.class, 202 | prop.methodName, 203 | variableName) 204 | .endControlFlow(); 205 | } 206 | 207 | return validationBlock.build(); 208 | } 209 | 210 | @Nullable 211 | private static T findAnnotationByAnnotation(Collection annotations, 213 | Class clazz) { 214 | if (annotations.isEmpty()) return null; // Save an iterator in the common case. 215 | for (AnnotationMirror mirror : annotations) { 216 | Annotation target = mirror.getAnnotationType() 217 | .asElement() 218 | .getAnnotation(clazz); 219 | if (target != null) { 220 | //noinspection unchecked 221 | return (T) target; 222 | } 223 | } 224 | return null; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-android-compiler-extension/src/main/java/io/sweers/inspector/extensions/android/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Android compiler extensions. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.extensions.android; 22 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-autovalue-compiler-extension/README.md: -------------------------------------------------------------------------------- 1 | inspector-autovalue-compiler-extension 2 | ====================================== 3 | 4 | An inspector compiler extension that tells the inspector compiler to look for `@AutoValue`-annotated 5 | classes to generate validators for. Note that you must still have a method returning a Validator, this 6 | just tells the compiler where to look for 'em. 7 | 8 | [![Maven Central](https://img.shields.io/maven-central/v/io.sweers.inspector/inspector-autovalue-compiler-extension.svg)](https://mvnrepository.com/artifact/io.sweers.inspector/inspector-autovalue-compiler-extension) 9 | ```gradle 10 | compileOnly 'io.sweers.inspector:inspector-autovalue-compiler-extension:x.y.z' 11 | ``` 12 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-autovalue-compiler-extension/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.errorprone' 20 | } 21 | 22 | sourceCompatibility = JavaVersion.VERSION_1_8 23 | targetCompatibility = JavaVersion.VERSION_1_8 24 | 25 | dependencies { 26 | api project(':inspector-compiler-extensions-api') 27 | compileOnly deps.misc.javaxExtras 28 | compileOnly deps.auto.service 29 | compileOnly deps.misc.errorProneAnnotations 30 | 31 | errorprone deps.build.errorProne 32 | } 33 | 34 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 35 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-autovalue-compiler-extension/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=AutoValue Inspector Extension 2 | POM_ARTIFACT_ID=inspector-autovalue-compiler-extension 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-autovalue-compiler-extension/src/main/java/io/sweers/inspector/extensions/autovalue/AutoValueInspectorExtension.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.extensions.autovalue; 2 | 3 | import com.google.auto.service.AutoService; 4 | import io.sweers.inspector.compiler.plugins.spi.InspectorExtension; 5 | import java.util.Collections; 6 | import java.util.Set; 7 | 8 | @AutoService(InspectorExtension.class) public final class AutoValueInspectorExtension 9 | implements InspectorExtension { 10 | 11 | @Override public Set applicableAnnotations() { 12 | return Collections.singleton("com.google.auto.value.AutoValue"); 13 | } 14 | 15 | @Override public String toString() { 16 | return AutoValueInspectorExtension.class.getSimpleName(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-autovalue-compiler-extension/src/main/java/io/sweers/inspector/extensions/autovalue/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * AutoValue compiler extensions. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.extensions.autovalue; 22 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-nullability-compiler-extension/README.md: -------------------------------------------------------------------------------- 1 | inspector-nullability-compiler-extension 2 | ======================================== 3 | 4 | An inspector compiler extension that generates nullability validation by checking for any `@Nullable` 5 | annotation. Everything is assumed not-null by default. For obvious reasons, this does not work on primitive 6 | or `Void` types. 7 | 8 | [![Maven Central](https://img.shields.io/maven-central/v/io.sweers.inspector/inspector-nullability-compiler-extension.svg)](https://mvnrepository.com/artifact/io.sweers.inspector/inspector-nullability-compiler-extension) 9 | ```gradle 10 | compileOnly 'io.sweers.inspector:inspector-nullability-compiler-extension:x.y.z' 11 | ``` 12 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-nullability-compiler-extension/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.errorprone' 20 | } 21 | 22 | sourceCompatibility = JavaVersion.VERSION_1_8 23 | targetCompatibility = JavaVersion.VERSION_1_8 24 | 25 | dependencies { 26 | api project(':inspector-compiler-extensions-api') 27 | compileOnly deps.misc.javaxExtras 28 | compileOnly deps.auto.service 29 | compileOnly deps.misc.errorProneAnnotations 30 | 31 | errorprone deps.build.errorProne 32 | } 33 | 34 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 35 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-nullability-compiler-extension/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Nullability Inspector Extension 2 | POM_ARTIFACT_ID=inspector-nullability-compiler-extension 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-nullability-compiler-extension/src/main/java/io/sweers/inspector/extensions/nullability/NullabilityInspectorExtension.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.extensions.nullability; 2 | 3 | import com.google.auto.service.AutoService; 4 | import com.squareup.javapoet.CodeBlock; 5 | import com.squareup.javapoet.ParameterSpec; 6 | import com.squareup.javapoet.TypeName; 7 | import io.sweers.inspector.ValidationException; 8 | import io.sweers.inspector.compiler.plugins.spi.InspectorExtension; 9 | import io.sweers.inspector.compiler.plugins.spi.Property; 10 | 11 | @AutoService(InspectorExtension.class) public final class NullabilityInspectorExtension 12 | implements InspectorExtension { 13 | 14 | @Override public boolean applicable(Property property) { 15 | return !property.type.isPrimitive() 16 | && !property.type.equals(TypeName.VOID.box()) 17 | && !property.annotations.contains("Nullable"); 18 | } 19 | 20 | @Override 21 | public CodeBlock generateValidation(Property prop, String variableName, ParameterSpec value) { 22 | return CodeBlock.builder() 23 | .beginControlFlow("if ($L == null)", variableName) 24 | .addStatement("throw new $T($S)", 25 | ValidationException.class, 26 | prop.methodName + "() is not nullable but returns a null") 27 | .endControlFlow() 28 | .build(); 29 | } 30 | 31 | @Override public Priority priority() { 32 | return Priority.HIGH; 33 | } 34 | 35 | @Override public String toString() { 36 | return NullabilityInspectorExtension.class.getSimpleName(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-nullability-compiler-extension/src/main/java/io/sweers/inspector/extensions/nullability/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Nullability compiler extensions. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.extensions.nullability; 22 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-rave-compiler-extension/README.md: -------------------------------------------------------------------------------- 1 | inspector-rave-compiler-extension 2 | ======================================== 3 | 4 | An inspector compiler extension that generates validation for RAVE annotations. 5 | 6 | Supported annotations: 7 | - MustBeTrue 8 | - MustBeFalse 9 | 10 | [![Maven Central](https://img.shields.io/maven-central/v/io.sweers.inspector/inspector-rave-compiler-extension.svg)](https://mvnrepository.com/artifact/io.sweers.inspector/inspector-rave-compiler-extension) 11 | ```gradle 12 | compileOnly 'io.sweers.inspector:inspector-rave-compiler-extension:x.y.z' 13 | ``` 14 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-rave-compiler-extension/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.errorprone' 20 | } 21 | 22 | sourceCompatibility = JavaVersion.VERSION_1_8 23 | targetCompatibility = JavaVersion.VERSION_1_8 24 | 25 | dependencies { 26 | api project(':inspector-compiler-extensions-api') 27 | api deps.misc.guava 28 | api deps.misc.javapoet 29 | api deps.misc.rave 30 | compileOnly deps.misc.javaxExtras 31 | compileOnly deps.auto.service 32 | compileOnly deps.misc.errorProneAnnotations 33 | 34 | errorprone deps.build.errorProne 35 | } 36 | 37 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 38 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-rave-compiler-extension/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=RAVE Inspector Extension 2 | POM_ARTIFACT_ID=inspector-rave-compiler-extension 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-rave-compiler-extension/src/main/java/io/sweers/inspector/extensions/rave/RaveInspectorExtension.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.extensions.rave; 2 | 3 | import com.google.auto.service.AutoService; 4 | import com.squareup.javapoet.CodeBlock; 5 | import com.squareup.javapoet.ParameterSpec; 6 | import com.squareup.javapoet.TypeName; 7 | import com.uber.rave.annotation.MustBeFalse; 8 | import com.uber.rave.annotation.MustBeTrue; 9 | import io.sweers.inspector.ValidationException; 10 | import io.sweers.inspector.compiler.plugins.spi.InspectorExtension; 11 | import io.sweers.inspector.compiler.plugins.spi.Property; 12 | import java.util.Objects; 13 | import javax.lang.model.element.ExecutableElement; 14 | import javax.lang.model.type.TypeMirror; 15 | 16 | @AutoService(InspectorExtension.class) public final class RaveInspectorExtension 17 | implements InspectorExtension { 18 | @Override public boolean applicable(Property property) { 19 | return property.element.getAnnotation(MustBeTrue.class) != null 20 | || property.element.getAnnotation(MustBeFalse.class) != null; 21 | } 22 | 23 | @Override 24 | public CodeBlock generateValidation(Property prop, String variableName, ParameterSpec value) { 25 | return addRaveChecks(prop.element, prop.methodName, value); 26 | } 27 | 28 | @Override public String toString() { 29 | return RaveInspectorExtension.class.getSimpleName(); 30 | } 31 | 32 | private static CodeBlock addRaveChecks(ExecutableElement element, 33 | String methodName, 34 | ParameterSpec value) { 35 | CodeBlock.Builder validationBlock = CodeBlock.builder(); 36 | MustBeTrue mustBeTrue = element.getAnnotation(MustBeTrue.class); 37 | if (mustBeTrue != null) { 38 | TypeMirror rType = element.getReturnType(); 39 | TypeName returnType = TypeName.get(rType); 40 | if (!Objects.equals(returnType, TypeName.BOOLEAN) && !Objects.equals(returnType, 41 | TypeName.BOOLEAN.box())) { 42 | throw new IllegalArgumentException( 43 | "@MustBeTrue can only be used on boolean return types but " 44 | + methodName 45 | + " returns " 46 | + returnType); 47 | } 48 | validationBlock.beginControlFlow("if (!$N.$L())", value, methodName) 49 | .addStatement("throw new $T(\"$L must be true but is false\")", 50 | ValidationException.class, 51 | methodName) 52 | .endControlFlow(); 53 | } 54 | MustBeFalse mustBeFalse = element.getAnnotation(MustBeFalse.class); 55 | if (mustBeFalse != null) { 56 | TypeMirror rType = element.getReturnType(); 57 | TypeName returnType = TypeName.get(rType); 58 | if (!Objects.equals(returnType, TypeName.BOOLEAN) && !Objects.equals(returnType, 59 | TypeName.BOOLEAN.box())) { 60 | throw new IllegalArgumentException( 61 | "@MustBeFalse can only be used on boolean return types but " 62 | + methodName 63 | + " returns " 64 | + returnType); 65 | } 66 | validationBlock.beginControlFlow("if ($N.$L())", value, methodName) 67 | .addStatement("throw new $T(\"$L must be false but is true\")", 68 | ValidationException.class, 69 | methodName) 70 | .endControlFlow(); 71 | } 72 | return validationBlock.build(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /compiler-extensions/inspector-rave-compiler-extension/src/main/java/io/sweers/inspector/extensions/rave/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * RAVE compiler extensions. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.extensions.rave; 22 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=io.sweers.inspector 2 | VERSION_NAME=0.3.1-SNAPSHOT 3 | POM_DESCRIPTION=A tiny class validation library. 4 | POM_URL=https://github.com/hzsweers/inspector/ 5 | POM_SCM_URL=https://github.com/hzsweers/inspector/ 6 | POM_SCM_CONNECTION=scm:git:git://github.com/hzsweers/inspector.git 7 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/hzsweers/inspector.git 8 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 9 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 10 | POM_LICENCE_DIST=repo 11 | POM_DEVELOPER_ID=hzsweers 12 | POM_DEVELOPER_NAME=Zac Sweers 13 | -------------------------------------------------------------------------------- /gradle/dependencies.gradle: -------------------------------------------------------------------------------- 1 | import org.gradle.internal.jvm.Jvm 2 | 3 | def versions = [ 4 | aptPlugin: '0.14', 5 | dokka: '0.9.16-eap-3', 6 | errorProne: '2.2.0', 7 | errorPronePlugin: '0.0.13', 8 | kotlin: '1.2.21', 9 | retrofit: '2.3.0' 10 | ] 11 | 12 | ext.deps = [ 13 | auto: [ 14 | value: "com.google.auto.value:auto-value:1.5.3", 15 | service: "com.google.auto.service:auto-service:1.0-rc4" 16 | ], 17 | build: [ 18 | errorProne: "com.google.errorprone:error_prone_core:${versions.errorProne}", 19 | 20 | repositories: [ 21 | kotlinEap: 'https://dl.bintray.com/kotlin/kotlin-eap' 22 | ], 23 | 24 | gradlePlugins: [ 25 | android: 'com.android.tools.build:gradle:2.3.0', 26 | dokka: "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}", 27 | dokkaAndroid: "org.jetbrains.dokka:dokka-android-gradle-plugin:${versions.dokka}", 28 | kotlin: "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" 29 | ] 30 | ], 31 | misc: [ 32 | guava: "com.google.guava:guava:24.0-jre", 33 | javapoet: "com.squareup:javapoet:1.10.0", 34 | javaxExtras: 'com.uber.javaxextras:javax-extras:0.1.0', 35 | errorProneAnnotations: "com.google.errorprone:error_prone_annotations:${versions.errorProne}", 36 | rave: 'com.uber:rave:2.0.0', 37 | retrofit: "com.squareup.retrofit2:retrofit:${versions.retrofit}" 38 | ], 39 | support: [ 40 | annotations: "com.android.support:support-annotations:27.1.0" 41 | ], 42 | test: [ 43 | compileTesting: 'com.google.testing.compile:compile-testing:0.13', 44 | junit: 'junit:junit:4.12', 45 | mockWebServer: "com.squareup.okhttp3:mockwebserver:3.9.1", 46 | retrofitMoshi: "com.squareup.retrofit2:converter-moshi:${versions.retrofit}", 47 | toolsJar: files(Jvm.current().getToolsJar()), 48 | truth: 'com.google.truth:truth:0.39' 49 | ], 50 | versions: versions, 51 | ] 52 | -------------------------------------------------------------------------------- /gradle/gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013. Chris Banes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | version = VERSION_NAME 21 | group = GROUP 22 | 23 | def isReleaseBuild() { 24 | return VERSION_NAME.contains("SNAPSHOT") == false 25 | } 26 | 27 | def getReleaseRepositoryUrl() { 28 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 29 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 30 | } 31 | 32 | def getSnapshotRepositoryUrl() { 33 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 34 | : "https://oss.sonatype.org/content/repositories/snapshots/" 35 | } 36 | 37 | def getRepositoryUsername() { 38 | return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : "" 39 | } 40 | 41 | def getRepositoryPassword() { 42 | return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : "" 43 | } 44 | 45 | afterEvaluate { project -> 46 | uploadArchives { 47 | repositories { 48 | mavenDeployer { 49 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 50 | 51 | pom.groupId = GROUP 52 | pom.artifactId = POM_ARTIFACT_ID 53 | pom.version = VERSION_NAME 54 | 55 | repository(url: getReleaseRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 59 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 60 | } 61 | 62 | pom.project { 63 | name POM_NAME 64 | packaging POM_PACKAGING 65 | description POM_DESCRIPTION 66 | url POM_URL 67 | 68 | scm { 69 | url POM_SCM_URL 70 | connection POM_SCM_CONNECTION 71 | developerConnection POM_SCM_DEV_CONNECTION 72 | } 73 | 74 | licenses { 75 | license { 76 | name POM_LICENCE_NAME 77 | url POM_LICENCE_URL 78 | distribution POM_LICENCE_DIST 79 | } 80 | } 81 | 82 | developers { 83 | developer { 84 | id POM_DEVELOPER_ID 85 | name POM_DEVELOPER_NAME 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | signing { 94 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 95 | sign configurations.archives 96 | } 97 | 98 | if (project.getPlugins().hasPlugin('com.android.application') || 99 | project.getPlugins().hasPlugin('com.android.library')) { 100 | task install(type: Upload, dependsOn: assemble) { 101 | repositories.mavenInstaller { 102 | configuration = configurations.archives 103 | 104 | pom.groupId = GROUP 105 | pom.artifactId = POM_ARTIFACT_ID 106 | pom.version = VERSION_NAME 107 | 108 | pom.project { 109 | name POM_NAME 110 | packaging POM_PACKAGING 111 | description POM_DESCRIPTION 112 | url POM_URL 113 | 114 | scm { 115 | url POM_SCM_URL 116 | connection POM_SCM_CONNECTION 117 | developerConnection POM_SCM_DEV_CONNECTION 118 | } 119 | 120 | licenses { 121 | license { 122 | name POM_LICENCE_NAME 123 | url POM_LICENCE_URL 124 | distribution POM_LICENCE_DIST 125 | } 126 | } 127 | 128 | developers { 129 | developer { 130 | id POM_DEVELOPER_ID 131 | name POM_DEVELOPER_NAME 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | task androidJavadocs(type: Javadoc) { 139 | if (!project.plugins.hasPlugin('org.jetbrains.kotlin.android')) { 140 | source = android.sourceSets.main.java.srcDirs 141 | } 142 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 143 | exclude '**/internal/*' 144 | 145 | if (JavaVersion.current().isJava8Compatible()) { 146 | options.addStringOption('Xdoclint:none', '-quiet') 147 | } 148 | } 149 | 150 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 151 | classifier = 'javadoc' 152 | from androidJavadocs.destinationDir 153 | } 154 | 155 | task androidSourcesJar(type: Jar) { 156 | classifier = 'sources' 157 | from android.sourceSets.main.java.sourceFiles 158 | } 159 | } else { 160 | install { 161 | repositories.mavenInstaller { 162 | pom.groupId = GROUP 163 | pom.artifactId = POM_ARTIFACT_ID 164 | pom.version = VERSION_NAME 165 | 166 | pom.project { 167 | name POM_NAME 168 | packaging POM_PACKAGING 169 | description POM_DESCRIPTION 170 | url POM_URL 171 | 172 | scm { 173 | url POM_SCM_URL 174 | connection POM_SCM_CONNECTION 175 | developerConnection POM_SCM_DEV_CONNECTION 176 | } 177 | 178 | licenses { 179 | license { 180 | name POM_LICENCE_NAME 181 | url POM_LICENCE_URL 182 | distribution POM_LICENCE_DIST 183 | } 184 | } 185 | 186 | developers { 187 | developer { 188 | id POM_DEVELOPER_ID 189 | name POM_DEVELOPER_NAME 190 | } 191 | } 192 | } 193 | } 194 | } 195 | 196 | task sourcesJar(type: Jar, dependsOn: classes) { 197 | classifier = 'sources' 198 | from sourceSets.main.allSource 199 | } 200 | 201 | task javadocJar(type: Jar, dependsOn: javadoc) { 202 | classifier = 'javadoc' 203 | from javadoc.destinationDir 204 | } 205 | } 206 | 207 | if (JavaVersion.current().isJava8Compatible()) { 208 | allprojects { 209 | tasks.withType(Javadoc) { 210 | options.addStringOption('Xdoclint:none', '-quiet') 211 | } 212 | } 213 | } 214 | 215 | artifacts { 216 | if (project.getPlugins().hasPlugin('com.android.application') || 217 | project.getPlugins().hasPlugin('com.android.library')) { 218 | archives androidSourcesJar 219 | archives androidJavadocsJar 220 | } else { 221 | archives sourcesJar 222 | archives javadocJar 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZacSweers/inspector/6ed606d90abf46c369adc7891ae240f22bff3567/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-all.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /inspector-compiler-annotations/README.md: -------------------------------------------------------------------------------- 1 | inspector-compiler-annotations 2 | ====================================== 3 | 4 | This artifact contains optional compile-time annotations for the `inspector-compiler`. 5 | These are extracted so you can use them as a `compileOnly` dependency in Android projects without 6 | placing the full annotation processor in the factory compiler artifact on your classpath. 7 | 8 | See the `inspector-compiler` README for full documentation. 9 | 10 | [![Maven Central](https://img.shields.io/maven-central/v/io.sweers.inspector/inspector-compiler-annotations.svg)](https://mvnrepository.com/artifact/io.sweers.inspector/inspector-compiler-annotations) 11 | ```gradle 12 | implementation 'io.sweers.inspector:inspector-compiler-annotations:x.y.z' 13 | ``` 14 | 15 | Proguard rules for obfuscation: 16 | 17 | ```proguard 18 | # Retain generated classes that end in the suffix 19 | -keepnames class Validator_** 20 | 21 | # Prevent obfuscation of types which use @GenerateValidator since the simple name 22 | # is used to reflectively look up the generated adapter. 23 | -keepnames @io.sweers.inspector.compiler.annotations.GenerateValidator class * 24 | ``` 25 | -------------------------------------------------------------------------------- /inspector-compiler-annotations/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.errorprone' 20 | } 21 | 22 | sourceCompatibility = JavaVersion.VERSION_1_7 23 | targetCompatibility = JavaVersion.VERSION_1_7 24 | 25 | dependencies { 26 | api project(':inspector') 27 | compileOnly deps.misc.javaxExtras 28 | } 29 | 30 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 31 | -------------------------------------------------------------------------------- /inspector-compiler-annotations/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Inspector Compiler Annotations 2 | POM_ARTIFACT_ID=inspector-compiler-annotations 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /inspector-compiler-annotations/src/main/java/io/sweers/inspector/compiler/annotations/GenerateValidator.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.compiler.annotations; 2 | 3 | import io.sweers.inspector.Inspector; 4 | import io.sweers.inspector.Types; 5 | import io.sweers.inspector.Validator; 6 | import java.lang.annotation.Annotation; 7 | import java.lang.annotation.Inherited; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | import java.lang.reflect.Constructor; 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.lang.reflect.ParameterizedType; 13 | import java.lang.reflect.Type; 14 | import java.util.LinkedHashMap; 15 | import java.util.Map; 16 | import java.util.Set; 17 | import javax.annotation.Nullable; 18 | 19 | import static java.lang.annotation.ElementType.TYPE; 20 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 21 | import static java.util.Collections.synchronizedMap; 22 | 23 | /** 24 | * Annotate a given type to indicate to inspector-compiler to generate its Validator in a separate 25 | * class regardless of a static validator method. There is an optional {@link #FACTORY} you can use 26 | * to leverage to reflectively look these up. 27 | */ 28 | @Inherited @Retention(RUNTIME) @Target(TYPE) public @interface GenerateValidator { 29 | 30 | Validator.Factory FACTORY = new Validator.Factory() { 31 | private final Map, Constructor> validators = 32 | synchronizedMap(new LinkedHashMap, Constructor>()); 33 | 34 | @SuppressWarnings("unchecked") @Nullable @Override public Validator create(Type type, 35 | Set annotations, 36 | Inspector inspector) { 37 | 38 | Class rawType = Types.getRawType(type); 39 | if (!rawType.isAnnotationPresent(GenerateValidator.class)) { 40 | return null; 41 | } 42 | 43 | Constructor constructor = findConstructorForClass(rawType); 44 | if (constructor == null) { 45 | return null; 46 | } 47 | //noinspection TryWithIdenticalCatches Resolves to API 19+ only type. 48 | try { 49 | if (constructor.getParameterTypes().length == 1) { 50 | return constructor.newInstance(inspector); 51 | } else { 52 | if (type instanceof ParameterizedType) { 53 | return constructor.newInstance(inspector, 54 | ((ParameterizedType) type).getActualTypeArguments()); 55 | } else { 56 | throw new IllegalStateException("Unable to handle type " + type); 57 | } 58 | } 59 | } catch (IllegalAccessException e) { 60 | throw new RuntimeException("Unable to invoke " + constructor, e); 61 | } catch (InstantiationException e) { 62 | throw new RuntimeException("Unable to invoke " + constructor, e); 63 | } catch (InvocationTargetException e) { 64 | Throwable cause = e.getCause(); 65 | if (cause instanceof RuntimeException) { 66 | throw (RuntimeException) cause; 67 | } 68 | if (cause instanceof Error) { 69 | throw (Error) cause; 70 | } 71 | throw new RuntimeException("Could not create generated TypeAdapter instance for type " 72 | + rawType, cause); 73 | } 74 | } 75 | 76 | @Nullable private Constructor findConstructorForClass(Class cls) { 77 | Constructor adapterCtor = validators.get(cls); 78 | if (adapterCtor != null) { 79 | return adapterCtor; 80 | } 81 | String clsName = cls.getSimpleName().replace("$", "_"); 82 | String packageName = cls.getPackage().getName(); 83 | if (clsName.startsWith("android.") 84 | || clsName.startsWith("java.") 85 | || clsName.startsWith("kotlin.")) { 86 | return null; 87 | } 88 | try { 89 | Class bindingClass = cls.getClassLoader() 90 | .loadClass(packageName + ".Validator_" + clsName); 91 | try { 92 | // Try the inspector constructor 93 | //noinspection unchecked 94 | adapterCtor = 95 | (Constructor) bindingClass.getConstructor(Inspector.class); 96 | adapterCtor.setAccessible(true); 97 | } catch (NoSuchMethodException e) { 98 | // Try the inspector + Type[] constructor 99 | //noinspection unchecked 100 | adapterCtor = 101 | (Constructor) bindingClass.getConstructor(Inspector.class, 102 | Type[].class); 103 | adapterCtor.setAccessible(true); 104 | } 105 | } catch (ClassNotFoundException e) { 106 | adapterCtor = findConstructorForClass(cls.getSuperclass()); 107 | } catch (NoSuchMethodException e) { 108 | throw new RuntimeException("Unable to find binding constructor for " + clsName, e); 109 | } 110 | validators.put(cls, adapterCtor); 111 | return adapterCtor; 112 | } 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /inspector-compiler-annotations/src/main/java/io/sweers/inspector/compiler/annotations/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Inspector compiler annotations. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.compiler.annotations; 22 | -------------------------------------------------------------------------------- /inspector-compiler-extensions-api/README.md: -------------------------------------------------------------------------------- 1 | inspector-compiler-extensions-api 2 | ================================= 3 | 4 | This is the main API entry point for creating inspector compiler extensions. 5 | 6 | The interface is the `InspectorExtension` interface, which has default implementations for its methods. 7 | 8 | There are four parts to the interface: 9 | 10 | `Set applicableAnnotations()` is for returning a set of annotations you want the annotation 11 | processor to find. These conventionally are going to be annotations on types, such as `AutoValue` classes. 12 | 13 | `boolean applicable(...)` is for checking if a given `Property` is applicable for this extension. For instance, 14 | a nullability extension would check if the property was a non-primitive/non-Void type. 15 | 16 | `CodeBlock generateValidation(...)` is for generating your actual validation. This returns a JavaPoet CodeBlock, and gives you 17 | information about three parameters: 18 | - `prop` - the property itself 19 | - `propertyName` - the name of the property being validated. If you are validating a `Person`'s `name` property, this is `name` 20 | - `typeInstance` - the instance of the type being validated. If you are validating a `Person` instance, this is the instance as passed to the `validate` method. 21 | 22 | If no validation is needed, you can return `null`. 23 | 24 | `Priority priority()` is for declaring priority of your extension. This is useful if you have higher priority validation 25 | that should run as early as possible (such as nullability). Most validations should not care what order they are run in though. 26 | Possible values are `HIGH`, `NORMAL`, and `NONE`. 27 | 28 | All properties are represented by the `Property` class, which is handed to you in `applicable` and `generateValidation`. 29 | This class has various information about a given property accessible via final fields as well as helper methods. 30 | 31 | To implement your own extension, simply implement `InspectorExtension` or extend `AbstractInspectorExtension` and 32 | mark it in your resources as a service. If you use AutoService, it's very simple: 33 | 34 | ```java 35 | @AutoService(InspectorExtension.class) 36 | public final class MyInspectorExtension extends AbstractInspectorExtension { 37 | // Your stuff here! 38 | } 39 | ``` 40 | 41 | [![Maven Central](https://img.shields.io/maven-central/v/io.sweers.inspector/inspector-compiler-extensions-api.svg)](https://mvnrepository.com/artifact/io.sweers.inspector/inspector-compiler-extensions-api) 42 | ```gradle 43 | implementation 'io.sweers.inspector:inspector-compiler-extensions-api:x.y.z' 44 | ``` 45 | -------------------------------------------------------------------------------- /inspector-compiler-extensions-api/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'net.ltgt.errorprone' 4 | } 5 | 6 | sourceCompatibility = JavaVersion.VERSION_1_8 7 | targetCompatibility = JavaVersion.VERSION_1_8 8 | 9 | dependencies { 10 | api project(':inspector') 11 | api deps.misc.guava 12 | api deps.misc.javapoet 13 | api deps.misc.javaxExtras 14 | compileOnly deps.misc.errorProneAnnotations 15 | 16 | errorprone deps.build.errorProne 17 | } 18 | 19 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 20 | -------------------------------------------------------------------------------- /inspector-compiler-extensions-api/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Inspector Compiler Extensions API 2 | POM_ARTIFACT_ID=inspector-compiler-extensions-api 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /inspector-compiler-extensions-api/src/main/java/io/sweers/inspector/compiler/plugins/spi/InspectorExtension.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.compiler.plugins.spi; 2 | 3 | import com.squareup.javapoet.CodeBlock; 4 | import com.squareup.javapoet.ParameterSpec; 5 | import java.util.Collections; 6 | import java.util.Set; 7 | import javax.annotation.Nullable; 8 | 9 | /** 10 | * Basic extension interface. 11 | */ 12 | public interface InspectorExtension { 13 | 14 | /** 15 | * @return any other applicable annotations you want to process. 16 | */ 17 | default Set applicableAnnotations() { 18 | return Collections.emptySet(); 19 | } 20 | 21 | /** 22 | * This is for checking if a given `Property` is applicable for this extension. For instance, 23 | * a nullability extension would check if the property was a non-primitive/non-Void type. 24 | * 25 | * @param property the property to check 26 | * @return true if applicable, false if not. 27 | */ 28 | default boolean applicable(Property property) { 29 | return false; 30 | } 31 | 32 | /** 33 | * This is for generating your actual validation. This uses a JavaPoet CodeBlock, and gives you 34 | * information about three parameters: 35 | * 36 | * @param prop the property itself 37 | * @param propertyName the name of the property being validated. 38 | * If you are validating a `Person`'s `name` property, this is `name` 39 | * @param typeInstance the instance of the type being validated. 40 | * If you are validating a `Person` instance, this is the instance as passed 41 | * to the `validate` method. 42 | * @return a codeblock of validation logic to execute, or null if there is none. 43 | */ 44 | @Nullable default CodeBlock generateValidation(Property prop, 45 | String propertyName, 46 | ParameterSpec typeInstance) { 47 | return null; 48 | } 49 | 50 | /** 51 | * This for declaring priority of your extension. This is useful if you have higher priority 52 | * validation that should run as early as possible (such as nullability). Most validations should 53 | * not care what order they are run in though. Default is {@link Priority#NONE}. 54 | * 55 | * @return the priority 56 | */ 57 | default Priority priority() { 58 | return Priority.NONE; 59 | } 60 | 61 | enum Priority { 62 | /** 63 | * Use this priority to indicate that this must be run as soon as possible, such as nullability 64 | * checks. 65 | */ 66 | HIGH, 67 | 68 | /** 69 | * Use this priority to indicate that sooner is better, but not important. 70 | */ 71 | NORMAL, 72 | 73 | /** 74 | * Use this priority to indicate that it does not matter when this runs, as long as it does. 75 | * This is the default mode. 76 | */ 77 | NONE 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /inspector-compiler-extensions-api/src/main/java/io/sweers/inspector/compiler/plugins/spi/Property.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.compiler.plugins.spi; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import com.squareup.javapoet.TypeName; 5 | import io.sweers.inspector.InspectorIgnored; 6 | import io.sweers.inspector.ValidatedBy; 7 | import java.lang.annotation.Annotation; 8 | import java.util.List; 9 | import java.util.Map; 10 | import javax.annotation.Nullable; 11 | import javax.lang.model.element.AnnotationMirror; 12 | import javax.lang.model.element.AnnotationValue; 13 | import javax.lang.model.element.Element; 14 | import javax.lang.model.element.ExecutableElement; 15 | import javax.lang.model.type.TypeMirror; 16 | 17 | public class Property { 18 | public final String methodName; 19 | public final String humanName; 20 | public final ExecutableElement element; 21 | public final TypeName type; 22 | public final ImmutableSet annotations; 23 | 24 | public Property(String humanName, ExecutableElement element) { 25 | this.methodName = element.getSimpleName() 26 | .toString(); 27 | this.humanName = humanName; 28 | this.element = element; 29 | 30 | type = TypeName.get(element.getReturnType()); 31 | annotations = buildAnnotations(element); 32 | } 33 | 34 | @Nullable static TypeMirror getAnnotationValue(Element foo, Class annotation) { 35 | AnnotationMirror am = getAnnotationMirror(foo, annotation); 36 | if (am == null) { 37 | return null; 38 | } 39 | AnnotationValue av = getAnnotationValue(am, "value"); 40 | return av == null ? null : (TypeMirror) av.getValue(); 41 | } 42 | 43 | @Nullable 44 | private static AnnotationMirror getAnnotationMirror(Element typeElement, Class clazz) { 45 | String clazzName = clazz.getName(); 46 | for (AnnotationMirror m : typeElement.getAnnotationMirrors()) { 47 | if (m.getAnnotationType() 48 | .toString() 49 | .equals(clazzName)) { 50 | return m; 51 | } 52 | } 53 | return null; 54 | } 55 | 56 | @Nullable 57 | private static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) { 58 | Map values = 59 | annotationMirror.getElementValues(); 60 | for (Map.Entry entry : values 61 | .entrySet()) { 62 | if (entry.getKey() 63 | .getSimpleName() 64 | .toString() 65 | .equals(key)) { 66 | return entry.getValue(); 67 | } 68 | } 69 | return null; 70 | } 71 | 72 | @Nullable public T annotation(Class annotation) { 73 | return element.getAnnotation(annotation); 74 | } 75 | 76 | @Nullable public ValidatedBy validatedBy() { 77 | return element.getAnnotation(ValidatedBy.class); 78 | } 79 | 80 | @Nullable public AnnotationMirror validatedByMirror() { 81 | return getAnnotationMirror(element, ValidatedBy.class); 82 | } 83 | 84 | public boolean shouldValidate() { 85 | return element.getAnnotation(InspectorIgnored.class) == null && validatedBy() == null; 86 | } 87 | 88 | private ImmutableSet buildAnnotations(ExecutableElement element) { 89 | ImmutableSet.Builder builder = ImmutableSet.builder(); 90 | 91 | List annotations = element.getAnnotationMirrors(); 92 | for (AnnotationMirror annotation : annotations) { 93 | builder.add(annotation.getAnnotationType() 94 | .asElement() 95 | .getSimpleName() 96 | .toString()); 97 | } 98 | 99 | return builder.build(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /inspector-compiler-extensions-api/src/main/java/io/sweers/inspector/compiler/plugins/spi/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Inspector compiler extensions API. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.compiler.plugins.spi; 22 | -------------------------------------------------------------------------------- /inspector-compiler/README.md: -------------------------------------------------------------------------------- 1 | inspector-compiler 2 | ================== 3 | 4 | An annotation processor that can generate `Validator` implementations. This does nothing by default, and 5 | relies on extensions to generate the real validation. 6 | 7 | See the `inspector-compiler-extensions-api` for information on writing your own extension, or the 8 | `compiler-extensions` directory for first-party extensions. 9 | 10 | 11 | [![Maven Central](https://img.shields.io/maven-central/v/io.sweers.inspector/inspector-compiler.svg)](https://mvnrepository.com/artifact/io.sweers.inspector/inspector-compiler) 12 | ```gradle 13 | compileOnly 'io.sweers.inspector:inspector-compiler:x.y.z' 14 | ``` 15 | -------------------------------------------------------------------------------- /inspector-compiler/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.errorprone' 20 | } 21 | 22 | sourceCompatibility = JavaVersion.VERSION_1_8 23 | targetCompatibility = JavaVersion.VERSION_1_8 24 | 25 | dependencies { 26 | implementation project(':inspector') 27 | api project(':inspector-compiler-annotations') 28 | api project(':inspector-compiler-extensions-api') 29 | 30 | compileOnly deps.misc.javaxExtras 31 | compileOnly deps.auto.service 32 | implementation deps.auto.value 33 | implementation deps.misc.guava 34 | api deps.misc.javapoet 35 | compileOnly deps.misc.errorProneAnnotations 36 | 37 | errorprone deps.build.errorProne 38 | 39 | testCompile project(':compiler-extensions:inspector-android-compiler-extension') 40 | testCompile project(':compiler-extensions:inspector-autovalue-compiler-extension') 41 | testCompile project(':compiler-extensions:inspector-nullability-compiler-extension') 42 | testCompile project(':compiler-extensions:inspector-rave-compiler-extension') 43 | testCompile deps.auto.service 44 | testCompile deps.test.junit 45 | testCompile deps.test.truth 46 | testCompile deps.test.compileTesting 47 | testCompile deps.test.toolsJar 48 | } 49 | 50 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 51 | -------------------------------------------------------------------------------- /inspector-compiler/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Inspector Compiler 2 | POM_ARTIFACT_ID=inspector-compiler 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /inspector-compiler/src/main/java/io/sweers/inspector/compiler/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Annotation processor for generating Inspector validators. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.compiler; 22 | -------------------------------------------------------------------------------- /inspector-compiler/src/test/java/io/sweers/inspector/compiler/InspectorProcessorTest.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.compiler; 2 | 3 | import com.google.testing.compile.Compilation; 4 | import com.google.testing.compile.JavaFileObjects; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | import javax.tools.JavaFileObject; 9 | import org.junit.Test; 10 | 11 | import static com.google.common.truth.Truth.assertAbout; 12 | import static com.google.common.truth.Truth.assertThat; 13 | import static com.google.testing.compile.Compiler.javac; 14 | import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; 15 | import static javax.tools.JavaFileObject.Kind.SOURCE; 16 | 17 | public final class InspectorProcessorTest { 18 | 19 | @Test public void test() { 20 | 21 | JavaFileObject person = JavaFileObjects.forResource("Person.java"); 22 | JavaFileObject dataValidator = JavaFileObjects.forResource("DateValidator.java"); 23 | 24 | assertAbout(javaSources()).that(Arrays.asList(person, dataValidator)) 25 | .withClasspathFrom(getClass().getClassLoader()) 26 | .processedWith(new InspectorProcessor()) 27 | .compilesWithoutError() 28 | .and() 29 | .generatesSources(JavaFileObjects.forResource("Validator_Person.java")); 30 | } 31 | 32 | @Test public void shouldIgnoreIfImplementsSelfValidating() { 33 | Compilation compilation = javac().withClasspathFrom(getClass().getClassLoader()) 34 | .withProcessors(new InspectorProcessor()) 35 | .compile(JavaFileObjects.forSourceLines("test.SelfValidatingFoo", 36 | "package test;\n" 37 | + "\n" 38 | + "import com.google.auto.value.AutoValue;\n" 39 | + "import io.sweers.inspector.Inspector;\n" 40 | + "import io.sweers.inspector.SelfValidating;\n" 41 | + "import io.sweers.inspector.ValidationException;\n" 42 | + "import io.sweers.inspector.Validator;\n" 43 | + "\n" 44 | + "@AutoValue abstract class SelfValidatingFoo implements SelfValidating {\n" 45 | + " @Override public final void validate(Inspector inspector) throws " 46 | + "ValidationException {\n" 47 | + " // Custom validation!\n" 48 | + " }\n" 49 | + " \n" 50 | + " public static Validator validator(Inspector inspector) {\n" 51 | + " return new Validator() {\n" 52 | + " @Override public void validate(SelfValidatingFoo selfValidatingFoo)\n" 53 | + " throws ValidationException {\n" 54 | + " \n" 55 | + " }\n" 56 | + " };\n" 57 | + " }\n" 58 | + "}")); 59 | 60 | List generatedJavaFiles = compilation.generatedSourceFiles() 61 | .stream() 62 | .filter(javaFileObject -> javaFileObject.getKind() == SOURCE) 63 | .collect(Collectors.toList()); 64 | assertThat(generatedJavaFiles).isEmpty(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /inspector-compiler/src/test/resources/DateValidator.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import io.sweers.inspector.ValidationException; 4 | import io.sweers.inspector.Validator; 5 | import java.util.Date; 6 | 7 | public final class DateValidator extends Validator { 8 | @Override public void validate(Date date) throws ValidationException { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /inspector-compiler/src/test/resources/Person.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import android.support.annotation.IntDef; 4 | import android.support.annotation.IntRange; 5 | import android.support.annotation.Nullable; 6 | import android.support.annotation.Size; 7 | import android.support.annotation.StringDef; 8 | import com.google.auto.value.AutoValue; 9 | import com.uber.rave.annotation.MustBeFalse; 10 | import com.uber.rave.annotation.MustBeTrue; 11 | import io.sweers.inspector.Inspector; 12 | import io.sweers.inspector.InspectorIgnored; 13 | import io.sweers.inspector.ValidatedBy; 14 | import io.sweers.inspector.Validator; 15 | import java.util.Date; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.Set; 19 | 20 | @AutoValue public abstract class Person { 21 | 22 | public static final String FOO = "foo"; 23 | public static final String FOO2 = "foo2"; 24 | public static final int FOO_INT = 0; 25 | 26 | @StringDef({ FOO, FOO2 }) public @interface StringDefChecked {} 27 | 28 | @IntDef(FOO_INT) public @interface IntDefChecked {} 29 | 30 | public abstract String firstName(); 31 | 32 | public abstract String lastName(); 33 | 34 | public abstract int[] favoriteNumbers(); 35 | 36 | public abstract List aList(); 37 | 38 | public abstract Map aMap(); 39 | 40 | public abstract Set favoriteFoods(); 41 | 42 | @StringDefChecked public abstract String stringDefChecked(); 43 | 44 | @IntDefChecked public abstract int intDefChecked(); 45 | 46 | @IntRange(from = 0) public abstract int age(); 47 | 48 | @Nullable public abstract String occupation(); 49 | 50 | @ValidatedBy(DateValidator.class) public abstract Date birthday(); 51 | 52 | @InspectorIgnored public abstract String uuid(); 53 | 54 | @Size(multiple = 2) public abstract List doublesOfStrings(); 55 | 56 | @Size(3) public abstract Map threePairs(); 57 | 58 | @Size(min = 3) public abstract Set atLeastThreeStrings(); 59 | 60 | @Size(max = 3) public abstract Set atMostThreeStrings(); 61 | 62 | @MustBeTrue public final boolean checkMustBeTrue() { 63 | return true; 64 | } 65 | 66 | @MustBeFalse public final boolean checkMustBeFalse() { 67 | return false; 68 | } 69 | 70 | public static Validator validator(Inspector inspector) { 71 | return new Validator_Person(inspector); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /inspector-compiler/src/test/resources/Validator_Person.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import io.sweers.inspector.Inspector; 4 | import io.sweers.inspector.Types; 5 | import io.sweers.inspector.ValidationException; 6 | import io.sweers.inspector.Validator; 7 | import java.util.Date; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | final class Validator_Person extends Validator { 13 | private final Validator firstNameValidator; 14 | 15 | private final Validator lastNameValidator; 16 | 17 | private final Validator favoriteNumbersValidator; 18 | 19 | private final Validator> aListValidator; 20 | 21 | private final Validator> aMapValidator; 22 | 23 | private final Validator> favoriteFoodsValidator; 24 | 25 | private final Validator stringDefCheckedValidator; 26 | 27 | private final Validator intDefCheckedValidator; 28 | 29 | private final Validator ageValidator; 30 | 31 | private final Validator occupationValidator; 32 | 33 | private final Validator birthdayValidator; 34 | 35 | private final Validator> doublesOfStringsValidator; 36 | 37 | private final Validator> threePairsValidator; 38 | 39 | private final Validator> atLeastThreeStringsValidator; 40 | 41 | private final Validator> atMostThreeStringsValidator; 42 | 43 | private final Validator checkMustBeTrueValidator; 44 | 45 | private final Validator checkMustBeFalseValidator; 46 | 47 | public Validator_Person(Inspector inspector) { 48 | this.firstNameValidator = inspector.validator(String.class); 49 | this.lastNameValidator = inspector.validator(String.class); 50 | this.favoriteNumbersValidator = inspector.validator(int[].class); 51 | this.aListValidator = inspector.validator(Types.newParameterizedType(List.class, String.class)); 52 | this.aMapValidator = inspector.validator(Types.newParameterizedType(Map.class, String.class, String.class)); 53 | this.favoriteFoodsValidator = inspector.validator(Types.newParameterizedType(Set.class, String.class)); 54 | this.stringDefCheckedValidator = inspector.validator(String.class); 55 | this.intDefCheckedValidator = inspector.validator(int.class); 56 | this.ageValidator = inspector.validator(int.class); 57 | this.occupationValidator = inspector.validator(String.class); 58 | this.birthdayValidator = new DateValidator(); 59 | this.doublesOfStringsValidator = inspector.validator(Types.newParameterizedType(List.class, String.class)); 60 | this.threePairsValidator = inspector.validator(Types.newParameterizedType(Map.class, String.class, String.class)); 61 | this.atLeastThreeStringsValidator = inspector.validator(Types.newParameterizedType(Set.class, String.class)); 62 | this.atMostThreeStringsValidator = inspector.validator(Types.newParameterizedType(Set.class, String.class)); 63 | this.checkMustBeTrueValidator = inspector.validator(boolean.class); 64 | this.checkMustBeFalseValidator = inspector.validator(boolean.class); 65 | } 66 | 67 | @Override 68 | public void validate(Person value) throws ValidationException { 69 | // Begin validation for "firstName()" 70 | String firstName = value.firstName(); 71 | 72 | // Validations contributed by "NullabilityInspectorExtension" 73 | if (firstName == null) { 74 | throw new ValidationException("firstName() is not nullable but returns a null"); 75 | } 76 | firstNameValidator.validate(firstName); 77 | 78 | // Begin validation for "lastName()" 79 | String lastName = value.lastName(); 80 | 81 | // Validations contributed by "NullabilityInspectorExtension" 82 | if (lastName == null) { 83 | throw new ValidationException("lastName() is not nullable but returns a null"); 84 | } 85 | lastNameValidator.validate(lastName); 86 | 87 | // Begin validation for "favoriteNumbers()" 88 | int[] favoriteNumbers = value.favoriteNumbers(); 89 | 90 | // Validations contributed by "NullabilityInspectorExtension" 91 | if (favoriteNumbers == null) { 92 | throw new ValidationException("favoriteNumbers() is not nullable but returns a null"); 93 | } 94 | favoriteNumbersValidator.validate(favoriteNumbers); 95 | 96 | // Begin validation for "aList()" 97 | List aList = value.aList(); 98 | 99 | // Validations contributed by "NullabilityInspectorExtension" 100 | if (aList == null) { 101 | throw new ValidationException("aList() is not nullable but returns a null"); 102 | } 103 | aListValidator.validate(aList); 104 | 105 | // Begin validation for "aMap()" 106 | Map aMap = value.aMap(); 107 | 108 | // Validations contributed by "NullabilityInspectorExtension" 109 | if (aMap == null) { 110 | throw new ValidationException("aMap() is not nullable but returns a null"); 111 | } 112 | aMapValidator.validate(aMap); 113 | 114 | // Begin validation for "favoriteFoods()" 115 | Set favoriteFoods = value.favoriteFoods(); 116 | 117 | // Validations contributed by "NullabilityInspectorExtension" 118 | if (favoriteFoods == null) { 119 | throw new ValidationException("favoriteFoods() is not nullable but returns a null"); 120 | } 121 | favoriteFoodsValidator.validate(favoriteFoods); 122 | 123 | // Begin validation for "stringDefChecked()" 124 | String stringDefChecked = value.stringDefChecked(); 125 | 126 | // Validations contributed by "NullabilityInspectorExtension" 127 | if (stringDefChecked == null) { 128 | throw new ValidationException("stringDefChecked() is not nullable but returns a null"); 129 | } 130 | // Validations contributed by "AndroidInspectorExtension" 131 | if (!("foo".equals(stringDefChecked) && "foo2".equals(stringDefChecked))) { 132 | throw new ValidationException("stringDefChecked's value must be within scope of its StringDef. Is " + stringDefChecked); 133 | } 134 | stringDefCheckedValidator.validate(stringDefChecked); 135 | 136 | // Begin validation for "intDefChecked()" 137 | int intDefChecked = value.intDefChecked(); 138 | 139 | // Validations contributed by "AndroidInspectorExtension" 140 | if (!(intDefChecked != 0)) { 141 | throw new ValidationException("intDefChecked's value must be within scope of its IntDef. Is " + intDefChecked); 142 | } 143 | intDefCheckedValidator.validate(intDefChecked); 144 | 145 | // Begin validation for "age()" 146 | int age = value.age(); 147 | 148 | // Validations contributed by "AndroidInspectorExtension" 149 | if (age < 0) { 150 | throw new ValidationException("age must be greater than 0 but is " + age); 151 | } 152 | ageValidator.validate(age); 153 | 154 | // Begin validation for "occupation()" 155 | String occupation = value.occupation(); 156 | 157 | occupationValidator.validate(occupation); 158 | 159 | // Begin validation for "doublesOfStrings()" 160 | List doublesOfStrings = value.doublesOfStrings(); 161 | 162 | // Validations contributed by "NullabilityInspectorExtension" 163 | if (doublesOfStrings == null) { 164 | throw new ValidationException("doublesOfStrings() is not nullable but returns a null"); 165 | } 166 | // Validations contributed by "AndroidInspectorExtension" 167 | int doublesOfStringsSize = doublesOfStrings.size(); 168 | if (doublesOfStringsSize % 2 != 0) { 169 | throw new ValidationException("doublesOfStrings's size must be a multiple of 2 but is " + doublesOfStringsSize); 170 | } 171 | doublesOfStringsValidator.validate(doublesOfStrings); 172 | 173 | // Begin validation for "threePairs()" 174 | Map threePairs = value.threePairs(); 175 | 176 | // Validations contributed by "NullabilityInspectorExtension" 177 | if (threePairs == null) { 178 | throw new ValidationException("threePairs() is not nullable but returns a null"); 179 | } 180 | // Validations contributed by "AndroidInspectorExtension" 181 | int threePairsSize = threePairs.size(); 182 | if (threePairsSize != 3) { 183 | throw new ValidationException("threePairs's size must be exactly 3 but is " + threePairsSize); 184 | } 185 | threePairsValidator.validate(threePairs); 186 | 187 | // Begin validation for "atLeastThreeStrings()" 188 | Set atLeastThreeStrings = value.atLeastThreeStrings(); 189 | 190 | // Validations contributed by "NullabilityInspectorExtension" 191 | if (atLeastThreeStrings == null) { 192 | throw new ValidationException("atLeastThreeStrings() is not nullable but returns a null"); 193 | } 194 | // Validations contributed by "AndroidInspectorExtension" 195 | int atLeastThreeStringsSize = atLeastThreeStrings.size(); 196 | if (atLeastThreeStringsSize < 3) { 197 | throw new ValidationException("atLeastThreeStrings's size must be greater than 3 but is " + atLeastThreeStringsSize); 198 | } 199 | atLeastThreeStringsValidator.validate(atLeastThreeStrings); 200 | 201 | // Begin validation for "atMostThreeStrings()" 202 | Set atMostThreeStrings = value.atMostThreeStrings(); 203 | 204 | // Validations contributed by "NullabilityInspectorExtension" 205 | if (atMostThreeStrings == null) { 206 | throw new ValidationException("atMostThreeStrings() is not nullable but returns a null"); 207 | } 208 | // Validations contributed by "AndroidInspectorExtension" 209 | int atMostThreeStringsSize = atMostThreeStrings.size(); 210 | if (atMostThreeStringsSize > 3) { 211 | throw new ValidationException("atMostThreeStrings's size must be less than 3 but is " + atMostThreeStringsSize); 212 | } 213 | atMostThreeStringsValidator.validate(atMostThreeStrings); 214 | 215 | // Begin validation for "checkMustBeTrue()" 216 | boolean checkMustBeTrue = value.checkMustBeTrue(); 217 | 218 | // Validations contributed by "RaveInspectorExtension" 219 | if (!value.checkMustBeTrue()) { 220 | throw new ValidationException("checkMustBeTrue must be true but is false"); 221 | } 222 | checkMustBeTrueValidator.validate(checkMustBeTrue); 223 | 224 | // Begin validation for "checkMustBeFalse()" 225 | boolean checkMustBeFalse = value.checkMustBeFalse(); 226 | 227 | // Validations contributed by "RaveInspectorExtension" 228 | if (value.checkMustBeFalse()) { 229 | throw new ValidationException("checkMustBeFalse must be false but is true"); 230 | } 231 | checkMustBeFalseValidator.validate(checkMustBeFalse); 232 | 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /inspector-factory-compiler-annotations/README.md: -------------------------------------------------------------------------------- 1 | inspector-factory-compiler-annotations 2 | ====================================== 3 | 4 | This artifact contains necessary compile-time annotations for the `inspector-factory-compiler`. 5 | These are extracted so you can use them as a `compileOnly` dependency in Android projects without 6 | placing the full annotation processor in the factory compiler artifact on your classpath. 7 | 8 | See the `inspector-factory-compiler` README for full documentation. 9 | 10 | [![Maven Central](https://img.shields.io/maven-central/v/io.sweers.inspector/inspector-factory-compiler-annotations.svg)](https://mvnrepository.com/artifact/io.sweers.inspector/inspector-factory-compiler-annotations) 11 | ```gradle 12 | implementation 'io.sweers.inspector:inspector-factory-compiler-annotations:x.y.z' 13 | ``` 14 | -------------------------------------------------------------------------------- /inspector-factory-compiler-annotations/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.errorprone' 20 | } 21 | 22 | sourceCompatibility = JavaVersion.VERSION_1_8 23 | targetCompatibility = JavaVersion.VERSION_1_8 24 | 25 | dependencies { 26 | api project(':inspector') 27 | compileOnly deps.misc.javaxExtras 28 | } 29 | 30 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 31 | -------------------------------------------------------------------------------- /inspector-factory-compiler-annotations/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Inspector Factory Compiler Annotations 2 | POM_ARTIFACT_ID=inspector-factory-compiler-annotations 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /inspector-factory-compiler-annotations/src/main/java/io/sweers/inspector/factorycompiler/InspectorFactory.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.factorycompiler; 2 | 3 | import io.sweers.inspector.Validator; 4 | import java.lang.annotation.Annotation; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.Target; 7 | 8 | import static java.lang.annotation.ElementType.TYPE; 9 | import static java.lang.annotation.RetentionPolicy.SOURCE; 10 | 11 | /** 12 | * Annotation to indicate that a given class should generate a concrete implementation of a 13 | * {@link Validator.Factory} that handles all the publicly denoted validator implementations of this 14 | * project. 15 | *

16 | *

17 |  *   @InspectorFactory(include = AutoValue.class)
18 |  *   public abstract class Factory implements Validator.Factory {
19 |  *     public static Factory create() {
20 |  *       return new InspectorFactory_Factory();
21 |  *     }
22 |  *   }
23 |  * 
24 | */ 25 | @Target(TYPE) @Retention(SOURCE) public @interface InspectorFactory { 26 | /** 27 | * @return an array of annotation types that should be included in this factory. 28 | */ 29 | Class[] include(); 30 | } 31 | -------------------------------------------------------------------------------- /inspector-factory-compiler-annotations/src/main/java/io/sweers/inspector/factorycompiler/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Annotations for the Inspector factory processor. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.factorycompiler; 22 | -------------------------------------------------------------------------------- /inspector-factory-compiler/README.md: -------------------------------------------------------------------------------- 1 | inspector-factory-compiler 2 | ========================== 3 | 4 | If you have a lot of validators (such as generated ones), you can use this annotation processor to 5 | generate a `Validator.Factory` implementation that composes all of these generated ones. 6 | 7 | Simply create an abstract class and annotate with `@InspectorFactory` (from the 8 | `inspector-factory-compiler-annotations` artifact) and implement `Validator.Factory`. 9 | A full implementation will be implemented in the same package with the prefix `InspectorFactory_` that 10 | extends the annotated class. 11 | 12 | You must specify (via the `include` argument) what classes to look for via annotation. For example, if you have validators on AutoValue 13 | classes, you could specify it as such. You can also write your own, and specify any number of annotation targets. 14 | 15 | ```java 16 | @InspectorFactory(include = AutoValue.class) 17 | public abstract class MyInspectorFactory implements Validator.Factory { 18 | 19 | public static MyInspectorFactory create() { 20 | return new InspectorFactory_MyInspectorFactory(); 21 | } 22 | 23 | } 24 | ``` 25 | 26 | [![Maven Central](https://img.shields.io/maven-central/v/io.sweers.inspector/inspector-factory-compiler.svg)](https://mvnrepository.com/artifact/io.sweers.inspector/inspector-factory-compiler) 27 | ```gradle 28 | implementation 'io.sweers.inspector:inspector-factory-compiler:x.y.z' 29 | ``` 30 | -------------------------------------------------------------------------------- /inspector-factory-compiler/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.errorprone' 20 | } 21 | 22 | sourceCompatibility = JavaVersion.VERSION_1_8 23 | targetCompatibility = JavaVersion.VERSION_1_8 24 | 25 | dependencies { 26 | api project(':inspector') 27 | api project(':inspector-factory-compiler-annotations') 28 | 29 | compileOnly deps.misc.javaxExtras 30 | compileOnly deps.auto.service 31 | implementation deps.auto.value 32 | implementation deps.misc.guava 33 | implementation deps.misc.javapoet 34 | compileOnly deps.misc.errorProneAnnotations 35 | 36 | errorprone deps.build.errorProne 37 | 38 | testCompile project(':compiler-extensions:inspector-autovalue-compiler-extension') 39 | testCompile project(':inspector-compiler') 40 | testCompile deps.test.junit 41 | testCompile deps.test.truth 42 | testCompile deps.test.compileTesting 43 | testCompile deps.test.toolsJar 44 | } 45 | 46 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 47 | -------------------------------------------------------------------------------- /inspector-factory-compiler/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Inspector Factory Compiler 2 | POM_ARTIFACT_ID=inspector-factory-compiler 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /inspector-factory-compiler/src/main/java/io/sweers/inspector/factorycompiler/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Annotation processor to generate Inspector Validator.Factory implementations. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.factorycompiler; 22 | -------------------------------------------------------------------------------- /inspector-factory-compiler/src/test/java/io/sweers/inspector/factorycompiler/InspectorFactoryProcessorTest.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.factorycompiler; 2 | 3 | import com.google.auto.value.processor.AutoValueProcessor; 4 | import com.google.testing.compile.JavaFileObjects; 5 | import io.sweers.inspector.compiler.InspectorProcessor; 6 | import javax.tools.JavaFileObject; 7 | import org.junit.Test; 8 | 9 | import static com.google.common.truth.Truth.assertAbout; 10 | import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; 11 | import static java.util.Arrays.asList; 12 | 13 | public final class InspectorFactoryProcessorTest { 14 | 15 | @Test public void smokeTest() { 16 | JavaFileObject factory = JavaFileObjects.forResource("MyFactory.java"); 17 | JavaFileObject person = JavaFileObjects.forResource("Person.java"); 18 | JavaFileObject person2 = JavaFileObjects.forResource("PersonTwo.java"); 19 | 20 | // Person 3 has no validator method, but that's a-ok! We should just ignore it 21 | JavaFileObject person3 = JavaFileObjects.forResource("PersonThree.java"); 22 | 23 | // Person 4 has a validator method with no args 24 | JavaFileObject person4 = JavaFileObjects.forResource("PersonFour.java"); 25 | 26 | // Person 5 is generic 27 | JavaFileObject person5 = JavaFileObjects.forResource("PersonFive.java"); 28 | 29 | assertAbout(javaSources()).that(asList(factory, person, person2, person3, person4, person5)) 30 | .withClasspathFrom(getClass().getClassLoader()) 31 | .processedWith(new InspectorProcessor(), 32 | new AutoValueProcessor(), 33 | new InspectorFactoryProcessor()) 34 | .compilesWithoutError() 35 | .and() 36 | .generatesSources(JavaFileObjects.forResource("InspectorFactory_MyFactory.java")); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /inspector-factory-compiler/src/test/resources/InspectorFactory_MyFactory.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import io.sweers.inspector.Inspector; 4 | import io.sweers.inspector.Validator; 5 | import java.lang.Override; 6 | import java.lang.annotation.Annotation; 7 | import java.lang.reflect.ParameterizedType; 8 | import java.lang.reflect.Type; 9 | import java.util.Set; 10 | 11 | public final class InspectorFactory_MyFactory extends MyFactory { 12 | @Override 13 | public Validator create(Type type, Set annotations, 14 | Inspector inspector) { 15 | if (!annotations.isEmpty()) return null; 16 | if (type instanceof ParameterizedType) { 17 | Type rawType = ((ParameterizedType) type).getRawType(); 18 | if (rawType.equals(PersonFive.class)) { 19 | return PersonFive.validator(inspector, ((ParameterizedType) type).getActualTypeArguments()); 20 | } 21 | return null; 22 | } 23 | if (type.equals(Person.class)) { 24 | return Person.validator(inspector); 25 | } else if (type.equals(PersonTwo.class)) { 26 | return PersonTwo.validator(inspector); 27 | } else if (type.equals(PersonFour.class)) { 28 | return PersonFour.validator(); 29 | } 30 | return null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /inspector-factory-compiler/src/test/resources/MyFactory.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import io.sweers.inspector.Validator; 5 | import io.sweers.inspector.factorycompiler.InspectorFactory; 6 | 7 | @InspectorFactory(include = AutoValue.class) public abstract class MyFactory 8 | implements Validator.Factory { 9 | public static MyFactory create() { 10 | return new InspectorFactory_MyFactory(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /inspector-factory-compiler/src/test/resources/Person.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import io.sweers.inspector.Inspector; 5 | import io.sweers.inspector.Validator; 6 | 7 | @AutoValue public abstract class Person { 8 | 9 | public abstract String firstName(); 10 | 11 | public static Validator validator(Inspector inspector) { 12 | return new Validator_Person(inspector); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /inspector-factory-compiler/src/test/resources/PersonFive.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import io.sweers.inspector.Inspector; 5 | import io.sweers.inspector.ValidationException; 6 | import io.sweers.inspector.Validator; 7 | import java.lang.reflect.Type; 8 | 9 | @AutoValue public abstract class PersonFive { 10 | 11 | public abstract String firstName(); 12 | public abstract T attribute(); 13 | public abstract V attribute2(); 14 | 15 | public static Validator> validator(Inspector inspector, Type[] types) { 16 | return new Validator>() { 17 | @Override public void validate(PersonFive personFour) throws ValidationException { 18 | 19 | } 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /inspector-factory-compiler/src/test/resources/PersonFour.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import io.sweers.inspector.ValidationException; 5 | import io.sweers.inspector.Validator; 6 | 7 | @AutoValue public abstract class PersonFour { 8 | 9 | public abstract String firstName(); 10 | 11 | public static Validator validator() { 12 | return new Validator() { 13 | @Override public void validate(PersonFour personFour) throws ValidationException { 14 | 15 | } 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /inspector-factory-compiler/src/test/resources/PersonThree.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import io.sweers.inspector.Inspector; 5 | import io.sweers.inspector.Validator; 6 | 7 | @AutoValue public abstract class PersonThree { 8 | 9 | public abstract String firstName(); 10 | } 11 | -------------------------------------------------------------------------------- /inspector-factory-compiler/src/test/resources/PersonTwo.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import io.sweers.inspector.Inspector; 5 | import io.sweers.inspector.Validator; 6 | 7 | @AutoValue public abstract class PersonTwo { 8 | 9 | public abstract String firstName(); 10 | 11 | public static Validator validator(Inspector inspector) { 12 | return new Validator_PersonTwo(inspector); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /inspector-retrofit/README.md: -------------------------------------------------------------------------------- 1 | inspector-retrofit 2 | ====================================== 3 | 4 | This artifact contains a delegating retrofit `Converter.Factory` implementation that you can use to 5 | validate response models. It works by simply delegating to other converter factories, then validating 6 | the final response value with a given `Inspector` instance and passes the result to a `ValidationExceptionCallback`. 7 | 8 | Because it's a delegating converter, you *must* add this factory before any other factories you want it 9 | to delegate to. 10 | 11 | Example usage: 12 | 13 | ```java 14 | Inspector inspector = new Inspector.Builder().build(); 15 | 16 | ValidationExceptionCallback callback = new ValidationExceptionCallback() { 17 | @Override public void onValidationException(Type type, ValidationException validationException) { 18 | // This response didn't pass validation! 19 | 20 | // You could log it 21 | System.out.println("Validation exception: " 22 | + type 23 | + ". Error: " 24 | + validationException.getMessage()); 25 | 26 | // Or throw it to fail hard 27 | throw validationException; 28 | 29 | // Or wrap in an IOException to drop it on the floor 30 | //throw new IOException(validationException); 31 | } 32 | }; 33 | 34 | Retrofit retrofit = new Retrofit.Builder() 35 | .baseUrl(...) 36 | .addConverterFactory(new InspectorConverterFactory(inspector, callback)) // Add this first! 37 | .addConverterFactory(...) // Add other converter factories after 38 | .build(); 39 | ``` 40 | 41 | [![Maven Central](https://img.shields.io/maven-central/v/io.sweers.inspector/inspector-retrofit.svg)](https://mvnrepository.com/artifact/io.sweers.inspector/inspector-retrofit) 42 | ```gradle 43 | implementation 'io.sweers.inspector:inspector-retrofit:x.y.z' 44 | ``` 45 | -------------------------------------------------------------------------------- /inspector-retrofit/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.apt-idea' 20 | id 'net.ltgt.errorprone' 21 | } 22 | 23 | sourceCompatibility = JavaVersion.VERSION_1_7 24 | targetCompatibility = JavaVersion.VERSION_1_7 25 | 26 | dependencies { 27 | compileOnly deps.misc.javaxExtras 28 | 29 | implementation project(':inspector') 30 | implementation deps.misc.retrofit 31 | 32 | errorprone deps.build.errorProne 33 | 34 | testAnnotationProcessor project(':compiler-extensions:inspector-autovalue-compiler-extension') 35 | testAnnotationProcessor deps.auto.value 36 | testAnnotationProcessor project(':inspector-compiler') 37 | testCompileOnly deps.auto.value 38 | testCompile deps.test.junit 39 | testCompile deps.test.mockWebServer 40 | testCompile deps.test.retrofitMoshi 41 | testCompile deps.test.truth 42 | } 43 | 44 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 45 | -------------------------------------------------------------------------------- /inspector-retrofit/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Inspector Retrofit 2 | POM_ARTIFACT_ID=inspector-retrofit 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /inspector-retrofit/src/main/java/io/sweers/inspector/retrofit/InspectorConverterFactory.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.retrofit; 2 | 3 | import io.sweers.inspector.Inspector; 4 | import io.sweers.inspector.ValidationException; 5 | import java.io.IOException; 6 | import java.lang.annotation.Annotation; 7 | import java.lang.reflect.Type; 8 | import okhttp3.ResponseBody; 9 | import retrofit2.Converter; 10 | import retrofit2.Retrofit; 11 | 12 | /** 13 | * An example implementation of a {@link Retrofit} {@link Converter.Factory} that delegates to a 14 | * later converter to create the response type, then validates it and throws it out if it fails 15 | * validation. 16 | */ 17 | public final class InspectorConverterFactory extends Converter.Factory { 18 | private final Inspector inspector; 19 | private final ValidationExceptionCallback callback; 20 | 21 | public InspectorConverterFactory(Inspector inspector, ValidationExceptionCallback callback) { 22 | this.inspector = inspector; 23 | this.callback = callback; 24 | } 25 | 26 | @Override public Converter responseBodyConverter(Type type, 27 | Annotation[] annotations, 28 | Retrofit retrofit) { 29 | 30 | Converter delegateConverter = 31 | retrofit.nextResponseBodyConverter(this, type, annotations); 32 | 33 | return new InspectorResponseConverter(type, inspector, callback, delegateConverter); 34 | } 35 | 36 | private static class InspectorResponseConverter implements Converter { 37 | 38 | private final Type type; 39 | private final Inspector inspector; 40 | private final ValidationExceptionCallback callback; 41 | private final Converter delegateConverter; 42 | 43 | InspectorResponseConverter(Type type, 44 | Inspector inspector, 45 | ValidationExceptionCallback callback, 46 | Converter delegateConverter) { 47 | this.type = type; 48 | this.inspector = inspector; 49 | this.callback = callback; 50 | this.delegateConverter = delegateConverter; 51 | } 52 | 53 | @Override public Object convert(ResponseBody value) throws IOException { 54 | Object convert = delegateConverter.convert(value); 55 | try { 56 | inspector.validator(type) 57 | .validate(convert); 58 | } catch (ValidationException validationException) { 59 | callback.onValidationException(type, validationException); 60 | } 61 | return convert; 62 | } 63 | } 64 | 65 | /** 66 | * A callback to be notified on validation exceptions and potentially act on them. Use cases could 67 | * include logging, throwing, etc. 68 | */ 69 | public interface ValidationExceptionCallback { 70 | 71 | /** 72 | * The callback method with the validation exception passed in. 73 | * 74 | * @param type the original type that failed. 75 | * @param exception the {@link ValidationException}. 76 | */ 77 | void onValidationException(Type type, ValidationException exception) throws IOException; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /inspector-retrofit/src/main/java/io/sweers/inspector/retrofit/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Inspector components for Retrofit. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.retrofit; 22 | -------------------------------------------------------------------------------- /inspector-retrofit/src/test/java/io/sweers/inspector/retrofit/InspectorConverterFactoryTest.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.retrofit; 2 | 3 | import io.sweers.inspector.Inspector; 4 | import io.sweers.inspector.SelfValidating; 5 | import io.sweers.inspector.ValidationException; 6 | import io.sweers.inspector.retrofit.InspectorConverterFactory.ValidationExceptionCallback; 7 | import java.io.IOException; 8 | import java.lang.reflect.Type; 9 | import java.util.concurrent.atomic.AtomicReference; 10 | import okhttp3.mockwebserver.MockResponse; 11 | import okhttp3.mockwebserver.MockWebServer; 12 | import org.junit.Rule; 13 | import org.junit.Test; 14 | import retrofit2.Call; 15 | import retrofit2.Response; 16 | import retrofit2.Retrofit; 17 | import retrofit2.converter.moshi.MoshiConverterFactory; 18 | import retrofit2.http.GET; 19 | 20 | import static com.google.common.truth.Truth.assertThat; 21 | 22 | public final class InspectorConverterFactoryTest { 23 | 24 | public static class Value implements SelfValidating { 25 | final String name; 26 | 27 | public Value(String name) { 28 | this.name = name; 29 | } 30 | 31 | @Override public void validate(Inspector inspector) throws ValidationException { 32 | if ("bad".equals(name)) { 33 | throw new ValidationException("name was bad!"); 34 | } 35 | } 36 | } 37 | 38 | interface Service { 39 | @GET("/") Call value(); 40 | } 41 | 42 | @Rule public final MockWebServer server = new MockWebServer(); 43 | 44 | private Inspector inspector = new Inspector.Builder().build(); 45 | 46 | @Test public void testBadData_throwing() throws IOException { 47 | ValidationExceptionCallback callback = new ValidationExceptionCallback() { 48 | @Override public void onValidationException(Type type, ValidationException exception) { 49 | throw exception; 50 | } 51 | }; 52 | Service service = createServiceForCallback(callback); 53 | 54 | server.enqueue(new MockResponse().setBody("{\"name\":\"bad\"}")); 55 | 56 | Call call = service.value(); 57 | 58 | try { 59 | Response response = call.execute(); 60 | throw new AssertionError("This should not be hit!"); 61 | } catch (ValidationException e) { 62 | assertThat(e).hasMessageThat().contains("name was bad!"); 63 | } 64 | } 65 | 66 | @Test public void testBadData_logging() throws IOException { 67 | final AtomicReference loggedCause = new AtomicReference<>(); 68 | ValidationExceptionCallback callback = new ValidationExceptionCallback() { 69 | @Override public void onValidationException(Type type, ValidationException exception) { 70 | loggedCause.set(exception.getMessage()); 71 | } 72 | }; 73 | Service service = createServiceForCallback(callback); 74 | 75 | server.enqueue(new MockResponse().setBody("{\"name\":\"bad\"}")); 76 | 77 | Call call = service.value(); 78 | Response response = call.execute(); 79 | Value value = response.body(); 80 | 81 | assertThat(value.name).isEqualTo("bad"); 82 | assertThat(loggedCause.get()).isNotNull(); 83 | assertThat(loggedCause.get()).contains("name was bad!"); 84 | } 85 | 86 | @Test public void testGoodData() throws IOException { 87 | ValidationExceptionCallback callback = new ValidationExceptionCallback() { 88 | @Override public void onValidationException(Type type, ValidationException exception) { 89 | throw exception; 90 | } 91 | }; 92 | Service service = createServiceForCallback(callback); 93 | 94 | server.enqueue(new MockResponse().setBody("{\"name\":\"good\"}")); 95 | 96 | Call call = service.value(); 97 | Response response = call.execute(); 98 | Value value = response.body(); 99 | 100 | assertThat(value.name).isEqualTo("good"); 101 | } 102 | 103 | private Service createServiceForCallback(ValidationExceptionCallback callback) { 104 | return new Retrofit.Builder() 105 | .baseUrl(server.url("/")) 106 | .addConverterFactory(new InspectorConverterFactory(inspector, callback)) 107 | .addConverterFactory(MoshiConverterFactory.create()) 108 | .build() 109 | .create(Service.class); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /inspector-sample-android/README.md: -------------------------------------------------------------------------------- 1 | inspector-android 2 | ================= 3 | 4 | This is a for-fun proof of concept of how runtime validation of android support library annotations would look. 5 | This unfortunately is not possible currently as the support annotations are not runtime-retained. 6 | 7 | -------------------------------------------------------------------------------- /inspector-sample-android/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.errorprone' 20 | } 21 | 22 | sourceCompatibility = JavaVersion.VERSION_1_7 23 | targetCompatibility = JavaVersion.VERSION_1_7 24 | 25 | dependencies { 26 | api project(':inspector') 27 | api deps.support.annotations 28 | compileOnly deps.misc.javaxExtras 29 | 30 | errorprone deps.build.errorProne 31 | 32 | testImplementation deps.test.junit 33 | testImplementation deps.test.truth 34 | } 35 | -------------------------------------------------------------------------------- /inspector-sample-android/src/main/java/io/sweers/inspector/android/IntRangeValidator.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.android; 2 | 3 | import android.support.annotation.IntRange; 4 | import android.support.annotation.Nullable; 5 | import io.sweers.inspector.Inspector; 6 | import io.sweers.inspector.ValidationException; 7 | import io.sweers.inspector.Validator; 8 | import java.lang.annotation.Annotation; 9 | import java.lang.reflect.Type; 10 | import java.util.Set; 11 | 12 | /** 13 | * A Validator for IntRange. Proof of concept, but not possible for now as these annotations are 14 | * only source. 15 | */ 16 | public final class IntRangeValidator extends Validator { 17 | 18 | public static Validator.Factory FACTORY = new Factory() { 19 | @Override public Validator create(Type type, 20 | Set annotations, 21 | Inspector inspector) { 22 | if (!annotations.isEmpty()) return null; 23 | Annotation annotation = findAnnotation(annotations, IntRange.class); 24 | if (annotation != null) { 25 | IntRange intRange = (IntRange) annotation; 26 | return new IntRangeValidator(intRange.from(), intRange.to()); 27 | } 28 | return null; 29 | } 30 | }; 31 | 32 | private final long from; 33 | private final long to; 34 | 35 | public IntRangeValidator(long from, long to) { 36 | this.from = from; 37 | this.to = to; 38 | } 39 | 40 | @Override public void validate(Integer value) throws ValidationException { 41 | if (value < from || value > to) { 42 | throw new ValidationException("Value was outside bounds"); 43 | } 44 | } 45 | 46 | @Nullable public static Annotation findAnnotation(Set annotations, 47 | Class annotationClass) { 48 | if (annotations.isEmpty()) return null; // Save an iterator in the common case. 49 | for (Annotation annotation : annotations) { 50 | if (annotation.annotationType() == annotationClass) return annotation; 51 | } 52 | return null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /inspector-sample-android/src/main/java/io/sweers/inspector/android/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Android runtime validation example. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.android; 22 | -------------------------------------------------------------------------------- /inspector-sample/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.apt-idea' 20 | id 'net.ltgt.errorprone' 21 | } 22 | 23 | sourceCompatibility = JavaVersion.VERSION_1_8 24 | targetCompatibility = JavaVersion.VERSION_1_8 25 | 26 | dependencies { 27 | annotationProcessor project(':inspector-compiler') 28 | annotationProcessor project(':inspector-factory-compiler') 29 | compileOnly project(':inspector-factory-compiler') 30 | annotationProcessor project(':compiler-extensions:inspector-android-compiler-extension') 31 | annotationProcessor project(':compiler-extensions:inspector-rave-compiler-extension') 32 | annotationProcessor project(':compiler-extensions:inspector-autovalue-compiler-extension') 33 | annotationProcessor deps.auto.value 34 | compileOnly deps.misc.javaxExtras 35 | compileOnly deps.auto.value 36 | compileOnly deps.misc.errorProneAnnotations 37 | compileOnly deps.misc.rave 38 | 39 | implementation project(':inspector') 40 | implementation project(':inspector-compiler-annotations') 41 | implementation project(':inspector-retrofit') 42 | implementation deps.misc.retrofit 43 | implementation deps.support.annotations 44 | 45 | errorprone deps.build.errorProne 46 | 47 | testAnnotationProcessor project(':compiler-extensions:inspector-autovalue-compiler-extension') 48 | testAnnotationProcessor deps.auto.value 49 | testAnnotationProcessor project(':inspector-compiler') 50 | testCompileOnly deps.auto.value 51 | testCompile deps.test.junit 52 | testCompile deps.test.truth 53 | } 54 | -------------------------------------------------------------------------------- /inspector-sample/src/main/java/io/sweers/inspector/sample/DateValidator.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import io.sweers.inspector.ValidationException; 4 | import io.sweers.inspector.Validator; 5 | import java.util.Date; 6 | 7 | public final class DateValidator extends Validator { 8 | @Override public void validate(Date object) throws ValidationException { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /inspector-sample/src/main/java/io/sweers/inspector/sample/Person.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import android.support.annotation.IntDef; 4 | import android.support.annotation.IntRange; 5 | import android.support.annotation.LongDef; 6 | import android.support.annotation.Nullable; 7 | import android.support.annotation.Size; 8 | import android.support.annotation.StringDef; 9 | import com.google.auto.value.AutoValue; 10 | import com.uber.rave.annotation.MustBeFalse; 11 | import com.uber.rave.annotation.MustBeTrue; 12 | import io.sweers.inspector.Inspector; 13 | import io.sweers.inspector.InspectorIgnored; 14 | import io.sweers.inspector.ValidatedBy; 15 | import io.sweers.inspector.Validator; 16 | import java.lang.reflect.Type; 17 | import java.util.Date; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.Set; 21 | 22 | @AutoValue public abstract class Person { 23 | 24 | public static final String FOO = "foo"; 25 | public static final String FOO2 = "foo2"; 26 | public static final int FOO_INT = 0; 27 | public static final long FOO_LONG = 0L; 28 | 29 | @StringDef({ FOO, FOO2 }) public @interface StringDefChecked {} 30 | 31 | @IntDef(FOO_INT) public @interface IntDefChecked {} 32 | 33 | @LongDef(FOO_LONG) public @interface LongDefChecked {} 34 | 35 | public abstract String firstName(); 36 | 37 | public abstract String lastName(); 38 | 39 | @SuppressWarnings("mutable") public abstract int[] favoriteNumbers(); 40 | 41 | public abstract List aList(); 42 | 43 | public abstract Map aMap(); 44 | 45 | public abstract Set favoriteFoods(); 46 | 47 | @StringDefChecked public abstract String stringDefChecked(); 48 | 49 | @IntDefChecked public abstract int intDefChecked(); 50 | 51 | @LongDefChecked public abstract long longDefChecked(); 52 | 53 | @IntRange(from = 0) public abstract int age(); 54 | 55 | @Nullable public abstract String occupation(); 56 | 57 | @ValidatedBy({ DateValidator.class, SecondaryDateValidator.class }) 58 | public abstract Date birthday(); 59 | 60 | @InspectorIgnored public abstract String uuid(); 61 | 62 | @Size(multiple = 2) public abstract List doublesOfStrings(); 63 | 64 | @Size(3) public abstract Map threePairs(); 65 | 66 | @Size(min = 3) public abstract Set atLeastThreeStrings(); 67 | 68 | @Size(max = 3) public abstract Set atMostThreeStrings(); 69 | 70 | @MustBeTrue public final boolean checkMustBeTrue() { 71 | return true; 72 | } 73 | 74 | @MustBeFalse public final boolean checkMustBeFalse() { 75 | return false; 76 | } 77 | 78 | public abstract T genericOne(); 79 | 80 | public abstract V genericTwo(); 81 | 82 | public static Validator> validator(Inspector inspector, Type[] types) { 83 | return new Validator_Person<>(inspector, types); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /inspector-sample/src/main/java/io/sweers/inspector/sample/RetrofitSample.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import io.sweers.inspector.Inspector; 4 | import io.sweers.inspector.retrofit.InspectorConverterFactory; 5 | import io.sweers.inspector.retrofit.InspectorConverterFactory.ValidationExceptionCallback; 6 | import okhttp3.OkHttpClient; 7 | import retrofit2.Retrofit; 8 | import retrofit2.http.GET; 9 | 10 | public final class RetrofitSample { 11 | 12 | public interface FooService { 13 | @GET("bar") Person getPerson(); 14 | } 15 | 16 | public static FooService createRetrofit() { 17 | Inspector inspector = new Inspector.Builder() 18 | .add(SampleFactory.create()) 19 | .build(); 20 | ValidationExceptionCallback callback = (type, validationException) -> { 21 | // This response didn't pass validation! 22 | 23 | // You could log it 24 | System.out.println("Validation exception: " 25 | + type 26 | + ". Error: " 27 | + validationException.getMessage()); 28 | 29 | // Or throw it to fail hard 30 | throw validationException; 31 | 32 | // Or wrap in an IOException to drop it on the floor 33 | //throw new IOException(validationException); 34 | }; 35 | FooService service = new Retrofit.Builder().callFactory(new OkHttpClient()) 36 | .baseUrl("https://example.com/") 37 | .validateEagerly(true) 38 | .addConverterFactory(new InspectorConverterFactory(inspector, callback)) 39 | .build() 40 | .create(FooService.class); 41 | 42 | return service; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /inspector-sample/src/main/java/io/sweers/inspector/sample/SampleFactory.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import io.sweers.inspector.Validator; 5 | import io.sweers.inspector.factorycompiler.InspectorFactory; 6 | 7 | @InspectorFactory(include = AutoValue.class) public abstract class SampleFactory 8 | implements Validator.Factory { 9 | 10 | public static SampleFactory create() { 11 | return new InspectorFactory_SampleFactory(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /inspector-sample/src/main/java/io/sweers/inspector/sample/SecondaryDateValidator.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import io.sweers.inspector.ValidationException; 4 | import io.sweers.inspector.Validator; 5 | import java.util.Date; 6 | 7 | public final class SecondaryDateValidator extends Validator { 8 | @Override public void validate(Date object) throws ValidationException { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /inspector-sample/src/main/java/io/sweers/inspector/sample/SelfValidatingPerson.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import io.sweers.inspector.Inspector; 5 | import io.sweers.inspector.SelfValidating; 6 | import io.sweers.inspector.ValidationException; 7 | 8 | @AutoValue public abstract class SelfValidatingPerson implements SelfValidating { 9 | 10 | @Override public final void validate(Inspector inspector) throws ValidationException { 11 | // Great! 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /inspector-sample/src/main/java/io/sweers/inspector/sample/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Inspector sample. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector.sample; 22 | -------------------------------------------------------------------------------- /inspector-sample/src/test/java/io/sweers/inspector/sample/integrationtest/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector.sample.integrationtest; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import io.sweers.inspector.Inspector; 5 | import io.sweers.inspector.Validator; 6 | import io.sweers.inspector.compiler.annotations.GenerateValidator; 7 | import java.lang.reflect.Type; 8 | import org.junit.Test; 9 | 10 | import static com.google.common.truth.Truth.assertThat; 11 | 12 | public final class IntegrationTest { 13 | 14 | @Test public void testGenerateValidator() { 15 | Inspector inspector = new Inspector.Builder().add(GenerateValidator.FACTORY) 16 | .build(); 17 | 18 | assertThat(inspector.validator(GenerateValidatorClazz.class)).isInstanceOf( 19 | Validator_GenerateValidatorClazz.class); 20 | } 21 | 22 | @GenerateValidator @AutoValue abstract static class GenerateValidatorClazz { 23 | 24 | abstract int foo(); 25 | } 26 | 27 | @AutoValue abstract static class GenerateValidatorClazzStatic { 28 | 29 | abstract int foo(); 30 | 31 | static Validator validator(Inspector inspector) { 32 | return new Validator_GenerateValidatorClazzStatic(inspector); 33 | } 34 | } 35 | 36 | @Test public void testStaticValidatorGeneric() { 37 | Inspector inspector = new Inspector.Builder().add(GenerateValidator.FACTORY) 38 | .build(); 39 | 40 | assertThat(inspector.validator(GenerateValidatorClazz.class)).isInstanceOf( 41 | Validator_GenerateValidatorClazz.class); 42 | } 43 | 44 | @AutoValue abstract static class GenerateValidatorClazzStaticGeneric { 45 | 46 | abstract int foo(); 47 | 48 | static Validator> validator(Inspector inspector, 49 | Type[] types) { 50 | return new Validator_GenerateValidatorClazzStaticGeneric<>(inspector, types); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /inspector/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'java-library' 19 | id 'net.ltgt.errorprone' 20 | } 21 | 22 | sourceCompatibility = "1.7" 23 | targetCompatibility = "1.7" 24 | 25 | dependencies { 26 | compileOnly deps.misc.javaxExtras 27 | compileOnly deps.misc.errorProneAnnotations 28 | 29 | errorprone deps.build.errorProne 30 | 31 | testImplementation deps.test.junit 32 | testImplementation deps.test.truth 33 | } 34 | 35 | apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 36 | -------------------------------------------------------------------------------- /inspector/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Inspector 2 | POM_ARTIFACT_ID=inspector 3 | POM_PACKAGING=jar 4 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/ArrayValidator.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Array; 5 | import java.lang.reflect.Type; 6 | import java.util.Set; 7 | import javax.annotation.Nullable; 8 | 9 | /** 10 | * Validates arrays. 11 | */ 12 | final class ArrayValidator extends Validator { 13 | public static final Factory FACTORY = new Factory() { 14 | @Override public @Nullable Validator create(Type type, 15 | Set annotations, 16 | Inspector inspector) { 17 | Type elementType = Types.arrayComponentType(type); 18 | if (elementType == null) return null; 19 | if (!annotations.isEmpty()) return null; 20 | Validator elementValidator = inspector.validator(elementType); 21 | return new ArrayValidator(elementValidator).nullSafe(); 22 | } 23 | }; 24 | 25 | private final Validator elementValidator; 26 | 27 | ArrayValidator(Validator elementValidator) { 28 | this.elementValidator = elementValidator; 29 | } 30 | 31 | @Override public void validate(Object validationTarget) throws ValidationException { 32 | for (int i = 0, size = Array.getLength(validationTarget); i < size; i++) { 33 | elementValidator.validate(Array.get(validationTarget, i)); 34 | } 35 | } 36 | 37 | @Override public String toString() { 38 | return elementValidator + ".array()"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/ClassFactory.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.io.ObjectInputStream; 4 | import java.io.ObjectStreamClass; 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * Magic that creates instances of arbitrary concrete classes. Derived from Gson's UnsafeAllocator 12 | * and ConstructorConstructor classes. 13 | * 14 | * @author Joel Leitch 15 | * @author Jesse Wilson 16 | */ 17 | @SuppressWarnings("LiteralClassName") 18 | abstract class ClassFactory { 19 | abstract T newInstance() throws 20 | InvocationTargetException, IllegalAccessException, InstantiationException; 21 | 22 | public static ClassFactory get(final Class rawType) { 23 | // Try to find a no-args constructor. May be any visibility including private. 24 | try { 25 | final Constructor constructor = rawType.getDeclaredConstructor(); 26 | constructor.setAccessible(true); 27 | return new ClassFactory() { 28 | @SuppressWarnings("unchecked") // T is the same raw type as is requested 29 | @Override public T newInstance() throws IllegalAccessException, InvocationTargetException, 30 | InstantiationException { 31 | Object[] args = null; 32 | //noinspection ConstantConditions because we want it to think it's an array to avoid vararg cost 33 | return (T) constructor.newInstance(args); 34 | } 35 | @Override public String toString() { 36 | return rawType.getName(); 37 | } 38 | }; 39 | } catch (NoSuchMethodException ignored) { 40 | // No no-args constructor. Fall back to something more magical... 41 | } 42 | 43 | // Try the JVM's Unsafe mechanism. 44 | // public class Unsafe { 45 | // public Object allocateInstance(Class type); 46 | // } 47 | try { 48 | Class unsafeClass = Class.forName("sun.misc.Unsafe"); 49 | Field f = unsafeClass.getDeclaredField("theUnsafe"); 50 | f.setAccessible(true); 51 | final Object unsafe = f.get(null); 52 | final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class); 53 | return new ClassFactory() { 54 | @SuppressWarnings("unchecked") 55 | @Override public T newInstance() throws InvocationTargetException, IllegalAccessException { 56 | return (T) allocateInstance.invoke(unsafe, rawType); 57 | } 58 | @Override public String toString() { 59 | return rawType.getName(); 60 | } 61 | }; 62 | } catch (IllegalAccessException e) { 63 | throw new AssertionError(); 64 | } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException ignored) { 65 | // Not the expected version of the Oracle Java library! 66 | } 67 | 68 | // Try (post-Gingerbread) Dalvik/libcore's ObjectStreamClass mechanism. 69 | // public class ObjectStreamClass { 70 | // private static native int getConstructorId(Class c); 71 | // private static native Object newInstance(Class instantiationClass, int methodId); 72 | // } 73 | try { 74 | Method getConstructorId = ObjectStreamClass.class.getDeclaredMethod( 75 | "getConstructorId", Class.class); 76 | getConstructorId.setAccessible(true); 77 | final int constructorId = (Integer) getConstructorId.invoke(null, Object.class); 78 | final Method newInstance = ObjectStreamClass.class.getDeclaredMethod("newInstance", 79 | Class.class, int.class); 80 | newInstance.setAccessible(true); 81 | return new ClassFactory() { 82 | @SuppressWarnings("unchecked") 83 | @Override public T newInstance() throws InvocationTargetException, IllegalAccessException { 84 | return (T) newInstance.invoke(null, rawType, constructorId); 85 | } 86 | @Override public String toString() { 87 | return rawType.getName(); 88 | } 89 | }; 90 | } catch (IllegalAccessException e) { 91 | throw new AssertionError(); 92 | } catch (InvocationTargetException e) { 93 | throw new RuntimeException(e); 94 | } catch (NoSuchMethodException ignored) { 95 | // Not the expected version of Dalvik/libcore! 96 | } 97 | 98 | // Try (pre-Gingerbread) Dalvik/libcore's ObjectInputStream mechanism. 99 | // public class ObjectInputStream { 100 | // private static native Object newInstance( 101 | // Class instantiationClass, Class constructorClass); 102 | // } 103 | try { 104 | final Method newInstance = ObjectInputStream.class.getDeclaredMethod( 105 | "newInstance", Class.class, Class.class); 106 | newInstance.setAccessible(true); 107 | return new ClassFactory() { 108 | @SuppressWarnings("unchecked") 109 | @Override public T newInstance() throws InvocationTargetException, IllegalAccessException { 110 | return (T) newInstance.invoke(null, rawType, Object.class); 111 | } 112 | @Override public String toString() { 113 | return rawType.getName(); 114 | } 115 | }; 116 | } catch (Exception ignored) { 117 | } 118 | 119 | throw new IllegalArgumentException("cannot construct instances of " + rawType.getName()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/ClassValidator.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Method; 6 | import java.lang.reflect.Modifier; 7 | import java.lang.reflect.Type; 8 | import java.util.Arrays; 9 | import java.util.Map; 10 | import java.util.Set; 11 | import java.util.TreeMap; 12 | import javax.annotation.Nullable; 13 | 14 | /** 15 | * Emits a validator that validates validatable methods of a class. 16 | * 17 | *

Platform Types

18 | * Fields from platform classes are omitted from both serialization and deserialization unless 19 | * they are either public or protected. This includes the following packages and their subpackages: 20 | * 21 | *
    22 | *
  • android.* 23 | *
  • java.* 24 | *
  • javax.* 25 | *
  • kotlin.* 26 | *
  • scala.* 27 | *
28 | */ 29 | final class ClassValidator extends Validator { 30 | public static final Validator.Factory FACTORY = new Validator.Factory() { 31 | @Override public @Nullable Validator create(Type type, 32 | Set annotations, 33 | Inspector inspector) { 34 | Class rawType = Types.getRawType(type); 35 | if (rawType.isInterface() || rawType.isEnum()) return null; 36 | if (isPlatformType(rawType) && !Types.isAllowedPlatformType(rawType)) { 37 | throw new IllegalArgumentException("Platform " 38 | + type 39 | + " annotated " 40 | + annotations 41 | + " requires explicit Validator to be registered"); 42 | } 43 | if (!annotations.isEmpty()) return null; 44 | 45 | if (rawType.getEnclosingClass() != null && !Modifier.isStatic(rawType.getModifiers())) { 46 | if (rawType.getSimpleName() 47 | .isEmpty()) { 48 | throw new IllegalArgumentException("Cannot validate anonymous class " 49 | + rawType.getName()); 50 | } else { 51 | throw new IllegalArgumentException("Cannot validate non-static nested class " 52 | + rawType.getName()); 53 | } 54 | } 55 | if (Modifier.isAbstract(rawType.getModifiers())) { 56 | throw new IllegalArgumentException("Cannot validate abstract class " + rawType.getName()); 57 | } 58 | 59 | ClassFactory classFactory = ClassFactory.get(rawType); 60 | Map> methods = new TreeMap<>(); 61 | for (Type t = type; t != Object.class; t = Types.getGenericSuperclass(t)) { 62 | createMethodBindings(inspector, t, methods); 63 | } 64 | return new ClassValidator<>(classFactory, methods).nullSafe(); 65 | } 66 | 67 | /** Creates a method binding for each of declared method of {@code type}. */ 68 | @SuppressWarnings("ClassNewInstance") private void createMethodBindings(Inspector inspector, 69 | Type type, 70 | Map> methodBindings) { 71 | Class rawType = Types.getRawType(type); 72 | boolean platformType = isPlatformType(rawType); 73 | for (final Method method : rawType.getDeclaredMethods()) { 74 | if (!includeMethod(platformType, method.getModifiers())) continue; 75 | if (method.getParameterTypes().length != 0) continue; 76 | if (method.getAnnotation(InspectorIgnored.class) != null) continue; 77 | 78 | // Look up a type validator for this type. 79 | Type returnType = Types.resolve(type, rawType, method.getGenericReturnType()); 80 | Set annotations = Util.validationAnnotations(method); 81 | Validator validator; 82 | ValidatedBy validatedBy = method.getAnnotation(ValidatedBy.class); 83 | if (validatedBy != null) { 84 | Class>[] validatorClasses = validatedBy.value(); 85 | if (validatorClasses.length == 0) { 86 | throw new IllegalArgumentException( 87 | "No validators specified in @ValidatedBy annotation on type " 88 | + returnType 89 | + "#" 90 | + method.getName()); 91 | } 92 | try { 93 | if (validatorClasses.length == 1) { 94 | //noinspection unchecked 95 | validator = (Validator) validatorClasses[0].newInstance(); 96 | } else { 97 | Validator[] validators = new Validator[validatorClasses.length]; 98 | for (int i = 0; i < validatorClasses.length; i++) { 99 | Class> clazz = validatorClasses[i]; 100 | validators[i] = clazz.newInstance(); 101 | } 102 | //noinspection unchecked 103 | validator = CompositeValidator.of(validators); 104 | } 105 | } catch (InstantiationException e) { 106 | throw new RuntimeException("Could not instantiate delegate validators " 107 | + Arrays.toString(validatedBy.value()) 108 | + " for " 109 | + method.getName() 110 | + ". Make sure they have public default constructors."); 111 | } catch (IllegalAccessException e) { 112 | throw new RuntimeException("Delegate validator " 113 | + Arrays.toString(validatedBy.value()) 114 | + " for " 115 | + method.getName() 116 | + " is not accessible. Make sure it has a public default constructor."); 117 | } 118 | } else { 119 | validator = inspector.validator(returnType, annotations); 120 | } 121 | 122 | boolean isPrimitive = method.getReturnType() 123 | .isPrimitive(); 124 | if (!isPrimitive && !Util.hasNullable(method.getDeclaredAnnotations())) { 125 | final Validator originalValidator = validator; 126 | validator = new Validator() { 127 | @Override public void validate(Object value) throws ValidationException { 128 | if (value == null) { 129 | throw new ValidationException("Returned value of " 130 | + method.getName() 131 | + "() was null."); 132 | } else { 133 | originalValidator.validate(value); 134 | } 135 | } 136 | }; 137 | } else if (!isPrimitive) { 138 | validator = validator.nullSafe(); 139 | } 140 | 141 | // Create the binding between method and validator. 142 | method.setAccessible(true); 143 | 144 | // Store it using the method's name. If there was already a method with this name, fail! 145 | String name = method.getName(); 146 | MethodBinding methodBinding = new MethodBinding<>(method, validator); 147 | MethodBinding replaced = methodBindings.put(name, methodBinding); 148 | if (replaced != null) { 149 | throw new IllegalArgumentException("Conflicting methods:\n" 150 | + " " 151 | + replaced.method 152 | + "\n" 153 | + " " 154 | + methodBinding.method); 155 | } 156 | } 157 | } 158 | 159 | /** Returns true if methods with {@code modifiers} are included in the emitted validator. */ 160 | private boolean includeMethod(boolean platformType, int modifiers) { 161 | return !Modifier.isStatic(modifiers) && (Modifier.isPublic(modifiers) || Modifier.isProtected( 162 | modifiers) || !platformType); 163 | } 164 | }; 165 | 166 | /** 167 | * Returns true if {@code rawType} is built in. We don't reflect on private methods of platform 168 | * types because they're unspecified and likely to be different on Java vs. Android. 169 | */ 170 | static boolean isPlatformType(Class rawType) { 171 | String name = rawType.getName(); 172 | return name.startsWith("android.") 173 | || name.startsWith("java.") 174 | || name.startsWith("javax.") 175 | || name.startsWith("kotlin.") 176 | || name.startsWith("scala."); 177 | } 178 | 179 | private final ClassFactory classFactory; 180 | private final MethodBinding[] methodsArray; 181 | 182 | ClassValidator(ClassFactory classFactory, Map> methodsMap) { 183 | this.classFactory = classFactory; 184 | this.methodsArray = methodsMap.values() 185 | .toArray(new MethodBinding[methodsMap.size()]); 186 | } 187 | 188 | @Override public String toString() { 189 | return "Validator(" + classFactory + ")"; 190 | } 191 | 192 | @Override public void validate(T validationTarget) throws ValidationException { 193 | for (MethodBinding methodBinding : methodsArray) { 194 | try { 195 | methodBinding.validate(validationTarget); 196 | } catch (IllegalAccessException e) { 197 | // Shouldn't happen, but just in case 198 | throw new ValidationException(methodBinding.method.getName() + " is inaccessible.", e); 199 | } catch (InvocationTargetException e) { 200 | throw new ValidationException(methodBinding.method.getName() 201 | + " threw an exception when called.", e); 202 | } 203 | } 204 | } 205 | 206 | static class MethodBinding { 207 | final Method method; 208 | final Validator validator; 209 | 210 | MethodBinding(Method method, Validator validator) { 211 | this.method = method; 212 | this.validator = validator; 213 | } 214 | 215 | @SuppressWarnings({ "unchecked", "RedundantThrows" }) void validate(Object validationTarget) 216 | throws IllegalAccessException, InvocationTargetException { 217 | validator.validate((T) method.invoke(validationTarget)); 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/CollectionValidator.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Type; 5 | import java.util.Collection; 6 | import java.util.List; 7 | import java.util.Set; 8 | import javax.annotation.Nullable; 9 | 10 | /** Validates collections. */ 11 | class CollectionValidator, T> extends Validator { 12 | public static final Validator.Factory FACTORY = new Validator.Factory() { 13 | @Override public @Nullable Validator create(Type type, 14 | Set annotations, 15 | Inspector inspector) { 16 | Class rawType = Types.getRawType(type); 17 | if (!annotations.isEmpty()) return null; 18 | if (rawType == List.class || rawType == Collection.class || rawType == Set.class) { 19 | return newCollectionValidator(type, inspector).nullSafe(); 20 | } 21 | return null; 22 | } 23 | }; 24 | 25 | private final Validator elementValidator; 26 | 27 | private CollectionValidator(Validator elementValidator) { 28 | this.elementValidator = elementValidator; 29 | } 30 | 31 | static Validator> newCollectionValidator(Type type, Inspector inspector) { 32 | Type elementType = Types.collectionElementType(type, Collection.class); 33 | Validator elementValidator = inspector.validator(elementType); 34 | return new CollectionValidator<>(elementValidator); 35 | } 36 | 37 | @Override public void validate(C validationTarget) throws ValidationException { 38 | for (T element : validationTarget) { 39 | elementValidator.validate(element); 40 | } 41 | } 42 | 43 | @Override public String toString() { 44 | return elementValidator + ".collection()"; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/CompositeValidationException.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.util.List; 4 | 5 | import static java.util.Collections.unmodifiableList; 6 | 7 | /** 8 | * A composite {@link ValidationException} that can hold and report multiple exceptions. 9 | */ 10 | public class CompositeValidationException extends ValidationException { 11 | 12 | private final List exceptions; 13 | 14 | CompositeValidationException(List exceptions) { 15 | super(createMessage(exceptions)); 16 | this.exceptions = exceptions; 17 | } 18 | 19 | /** 20 | * @return the list of discovered exceptions. 21 | */ 22 | public List getExceptions() { 23 | return unmodifiableList(exceptions); 24 | } 25 | 26 | private static String createMessage(List exceptions) { 27 | StringBuilder builder = new StringBuilder(); 28 | builder.append("Multiple validation exceptions found! Exceptions: [\n") 29 | .append(exceptions.get(0) 30 | .getMessage()); 31 | if (exceptions.size() > 1) { 32 | for (int i = 1; i < exceptions.size(); i++) { 33 | ValidationException exception = exceptions.get(i); 34 | builder.append(",\n") 35 | .append(exception.getMessage()); 36 | } 37 | } 38 | return builder.append("\n]") 39 | .toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/CompositeValidator.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import static java.util.Arrays.asList; 7 | import static java.util.Collections.unmodifiableList; 8 | 9 | /** 10 | * A convenience {@link Validator} that can compose multiple validators. 11 | */ 12 | public final class CompositeValidator extends Validator { 13 | 14 | @SafeVarargs public static CompositeValidator of(Validator... validators) { 15 | if (validators == null) { 16 | throw new NullPointerException("No validators received!"); 17 | } 18 | return of(asList(validators)); 19 | } 20 | 21 | public static CompositeValidator of(Iterable> validators) { 22 | if (validators == null) { 23 | throw new NullPointerException("validators are null"); 24 | } 25 | ArrayList> list = new ArrayList<>(); 26 | for (Validator validator : validators) { 27 | list.add(validator); 28 | } 29 | return new CompositeValidator<>(list); 30 | } 31 | 32 | public static CompositeValidator of(List> validators) { 33 | if (validators == null) { 34 | throw new NullPointerException("validators are null"); 35 | } 36 | return new CompositeValidator<>(unmodifiableList(validators)); 37 | } 38 | 39 | private final List> validators; 40 | 41 | private CompositeValidator(List> validators) { 42 | this.validators = validators; 43 | } 44 | 45 | @Override public void validate(T t) throws CompositeValidationException { 46 | List exceptions = new ArrayList<>(); 47 | for (Validator validator : validators) { 48 | try { 49 | validator.validate(t); 50 | } catch (ValidationException e) { 51 | exceptions.add(e); 52 | } 53 | } 54 | if (!exceptions.isEmpty()) { 55 | if (exceptions.size() == 1) { 56 | throw exceptions.get(0); 57 | } else { 58 | throw new CompositeValidationException(exceptions); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/Inspector.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Type; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.LinkedHashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Set; 12 | import javax.annotation.Nullable; 13 | 14 | /** 15 | * Validates objects. 16 | */ 17 | public final class Inspector { 18 | private static final List BUILT_IN_FACTORIES = new ArrayList<>(5); 19 | 20 | static { 21 | BUILT_IN_FACTORIES.add(StandardValidators.FACTORY); 22 | BUILT_IN_FACTORIES.add(CollectionValidator.FACTORY); 23 | BUILT_IN_FACTORIES.add(MapValidator.FACTORY); 24 | BUILT_IN_FACTORIES.add(ArrayValidator.FACTORY); 25 | BUILT_IN_FACTORIES.add(SelfValidating.FACTORY); 26 | BUILT_IN_FACTORIES.add(ClassValidator.FACTORY); 27 | } 28 | 29 | @SuppressWarnings("ThreadLocalUsage") 30 | private final ThreadLocal>> reentrantCalls = new ThreadLocal<>(); 31 | private final List factories; 32 | private final Map> adapterCache = new LinkedHashMap<>(); 33 | 34 | Inspector(Builder builder) { 35 | List factories = 36 | new ArrayList<>(builder.factories.size() + BUILT_IN_FACTORIES.size()); 37 | factories.addAll(builder.factories); 38 | factories.addAll(BUILT_IN_FACTORIES); 39 | this.factories = Collections.unmodifiableList(factories); 40 | } 41 | 42 | /** Returns a validator for {@code type}, creating it if necessary. */ 43 | public Validator validator(Type type) { 44 | return validator(type, Util.NO_ANNOTATIONS); 45 | } 46 | 47 | /** Returns a validator for {@code type}, creating it if necessary. */ 48 | public Validator validator(Class type) { 49 | return validator(type, Util.NO_ANNOTATIONS); 50 | } 51 | 52 | 53 | /** Returns a validator for {@code type} with {@code annotationType}, creating it if necessary. */ 54 | public Validator validator(Type type, Class annotationType) { 55 | return validator(type, 56 | Collections.singleton(Types.createValidationQualifierImplementation(annotationType))); 57 | } 58 | 59 | /** Returns a validator for {@code type} and {@code annotations}, creating it if necessary. */ 60 | @SuppressWarnings("unchecked") // Factories are required to return only matching Validators. 61 | public Validator validator(Type type, Set annotations) { 62 | type = Types.canonicalize(type); 63 | 64 | // If there's an equivalent adapter in the cache, we're done! 65 | Object cacheKey = cacheKey(type, annotations); 66 | synchronized (adapterCache) { 67 | Validator result = adapterCache.get(cacheKey); 68 | if (result != null) return (Validator) result; 69 | } 70 | 71 | // Short-circuit if this is a reentrant call. 72 | List> deferredAdapters = reentrantCalls.get(); 73 | if (deferredAdapters != null) { 74 | for (DeferredAdapter deferredAdapter : deferredAdapters) { 75 | if (deferredAdapter.cacheKey == null) { 76 | // not ready 77 | continue; 78 | } 79 | if (deferredAdapter.cacheKey.equals(cacheKey)) { 80 | return (Validator) deferredAdapter; 81 | } 82 | } 83 | } else { 84 | deferredAdapters = new ArrayList<>(); 85 | reentrantCalls.set(deferredAdapters); 86 | } 87 | 88 | // Prepare for re-entrant calls, then ask each factory to create a type adapter. 89 | DeferredAdapter deferredAdapter = new DeferredAdapter<>(cacheKey); 90 | deferredAdapters.add(deferredAdapter); 91 | try { 92 | for (Validator.Factory factory : factories) { 93 | Validator result = (Validator) factory.create(type, annotations, this); 94 | if (result != null) { 95 | deferredAdapter.ready(result); 96 | synchronized (adapterCache) { 97 | adapterCache.put(cacheKey, result); 98 | } 99 | return result; 100 | } 101 | } 102 | } finally { 103 | deferredAdapters.remove(deferredAdapters.size() - 1); 104 | if (deferredAdapters.isEmpty()) { 105 | reentrantCalls.remove(); 106 | } 107 | } 108 | 109 | throw new IllegalArgumentException("No Validator for " + type + " annotated " + annotations); 110 | } 111 | 112 | 113 | /** 114 | * Returns a validator for {@code type} and {@code annotations}, always creating a new one and 115 | * skipping past {@code skipPast} for creation. */ 116 | @SuppressWarnings("unchecked") // Factories are required to return only matching Validators. 117 | public Validator nextValidator(Validator.Factory skipPast, 118 | Type type, 119 | Set annotations) { 120 | type = Types.canonicalize(type); 121 | 122 | int skipPastIndex = factories.indexOf(skipPast); 123 | if (skipPastIndex == -1) { 124 | throw new IllegalArgumentException("Unable to skip past unknown factory " + skipPast); 125 | } 126 | for (int i = skipPastIndex + 1, size = factories.size(); i < size; i++) { 127 | Validator result = (Validator) factories.get(i) 128 | .create(type, annotations, this); 129 | if (result != null) return result; 130 | } 131 | throw new IllegalArgumentException("No next Validator for " 132 | + type 133 | + " annotated " 134 | + annotations); 135 | } 136 | 137 | /** Returns a new builder containing all custom factories used by the current instance. */ 138 | public Inspector.Builder newBuilder() { 139 | int fullSize = factories.size(); 140 | int tailSize = BUILT_IN_FACTORIES.size(); 141 | List customFactories = factories.subList(0, fullSize - tailSize); 142 | return new Builder().addAll(customFactories); 143 | } 144 | 145 | /** Returns an opaque object that's equal if the type and annotations are equal. */ 146 | private Object cacheKey(Type type, Set annotations) { 147 | if (annotations.isEmpty()) return type; 148 | return Arrays.asList(type, annotations); 149 | } 150 | 151 | public static final class Builder { 152 | final List factories = new ArrayList<>(); 153 | 154 | public Builder add(final Type type, final Validator validator) { 155 | if (type == null) throw new IllegalArgumentException("type == null"); 156 | if (validator == null) throw new IllegalArgumentException("validator == null"); 157 | 158 | return add(new Validator.Factory() { 159 | @Override public @Nullable Validator create(Type targetType, 160 | Set annotations, 161 | Inspector inspector) { 162 | return annotations.isEmpty() && Util.typesMatch(type, targetType) ? validator : null; 163 | } 164 | }); 165 | } 166 | 167 | public Builder add(final Type type, 168 | final Class annotation, 169 | final Validator validator) { 170 | if (type == null) throw new IllegalArgumentException("type == null"); 171 | if (annotation == null) throw new IllegalArgumentException("annotation == null"); 172 | if (validator == null) throw new IllegalArgumentException("validator == null"); 173 | if (!annotation.isAnnotationPresent(ValidationQualifier.class)) { 174 | throw new IllegalArgumentException(annotation + " does not have @ValidationQualifier"); 175 | } 176 | if (annotation.getDeclaredMethods().length > 0) { 177 | throw new IllegalArgumentException("Use Validator.Factory for annotations with elements"); 178 | } 179 | 180 | return add(new Validator.Factory() { 181 | @Override public @Nullable Validator create(Type targetType, 182 | Set annotations, 183 | Inspector inspector) { 184 | if (Util.typesMatch(type, targetType) 185 | && annotations.size() == 1 186 | && Util.isAnnotationPresent(annotations, annotation)) { 187 | return validator; 188 | } 189 | return null; 190 | } 191 | }); 192 | } 193 | 194 | public Builder add(Validator.Factory factory) { 195 | if (factory == null) throw new IllegalArgumentException("factory == null"); 196 | factories.add(factory); 197 | return this; 198 | } 199 | 200 | Builder addAll(List factories) { 201 | this.factories.addAll(factories); 202 | return this; 203 | } 204 | 205 | public Inspector build() { 206 | return new Inspector(this); 207 | } 208 | } 209 | 210 | /** 211 | * Sometimes a type adapter factory depends on its own product; either directly or indirectly. 212 | * To make this work, we offer this type adapter stub while the final adapter is being computed. 213 | * When it is ready, we wire this to delegate to that finished adapter. 214 | * 215 | *

Typically this is necessary in self-referential object models, such as an {@code Employee} 216 | * class that has a {@code List} field for an organization's management hierarchy. 217 | */ 218 | private static class DeferredAdapter extends Validator { 219 | @Nullable Object cacheKey; 220 | @Nullable private Validator delegate; 221 | 222 | DeferredAdapter(Object cacheKey) { 223 | this.cacheKey = cacheKey; 224 | } 225 | 226 | void ready(Validator delegate) { 227 | this.delegate = delegate; 228 | this.cacheKey = null; 229 | } 230 | 231 | @Override public void validate(T validationTarget) throws ValidationException { 232 | if (delegate == null) throw new IllegalStateException("Validator isn't ready"); 233 | delegate.validate(validationTarget); 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/InspectorIgnored.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.METHOD; 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | @Retention(RUNTIME) @Target(METHOD) public @interface InspectorIgnored {} 10 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/MapValidator.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Type; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import javax.annotation.Nullable; 8 | 9 | /** 10 | * Validates maps. 11 | */ 12 | final class MapValidator extends Validator> { 13 | public static final Factory FACTORY = new Factory() { 14 | @Override public @Nullable Validator create(Type type, 15 | Set annotations, 16 | Inspector inspector) { 17 | if (!annotations.isEmpty()) return null; 18 | Class rawType = Types.getRawType(type); 19 | if (rawType != Map.class) return null; 20 | Type[] keyAndValue = Types.mapKeyAndValueTypes(type, rawType); 21 | return new MapValidator<>(inspector, keyAndValue[0], keyAndValue[1]).nullSafe(); 22 | } 23 | }; 24 | 25 | private final Validator keyAdapter; 26 | private final Validator valueAdapter; 27 | 28 | MapValidator(Inspector inspector, Type keyType, Type valueType) { 29 | this.keyAdapter = inspector.validator(keyType); 30 | this.valueAdapter = inspector.validator(valueType); 31 | } 32 | 33 | @Override public void validate(Map map) throws ValidationException { 34 | for (Map.Entry entry : map.entrySet()) { 35 | if (entry.getKey() == null) { 36 | throw new ValidationException("Map key is null at"); 37 | } 38 | keyAdapter.validate(entry.getKey()); 39 | valueAdapter.validate(entry.getValue()); 40 | } 41 | } 42 | 43 | @Override public String toString() { 44 | return "Validator(" + keyAdapter + "=" + valueAdapter + ")"; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/SelfValidating.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Type; 5 | import java.util.Set; 6 | import javax.annotation.Nullable; 7 | 8 | /** 9 | * An interface that a given type can implement to let Inspector know that it validates itself. 10 | * 11 | *


12 |  *   class Foo implements SelfValidating {
13 |  *      @Override public void validate(Inspector inspector) throws ValidationException {
14 |  *        // Implement custom validation logic here.
15 |  *      }
16 |  *   }
17 |  * 
18 | */ 19 | public interface SelfValidating { 20 | /** 21 | * Validates this object with whatever custom validation implementation the user wants. 22 | * 23 | * @throws ValidationException upon invalidation 24 | */ 25 | void validate(Inspector inspector) throws ValidationException; 26 | 27 | /** 28 | * A factory instance for this. This is not considered public API. 29 | */ 30 | Validator.Factory FACTORY = new Validator.Factory() { 31 | 32 | @Nullable @Override public Validator create(final Type type, 33 | final Set annotations, 34 | final Inspector inspector) { 35 | if (SelfValidating.class.isAssignableFrom(Types.getRawType(type))) { 36 | return new Validator() { 37 | @Override public void validate(SelfValidating target) throws ValidationException { 38 | target.validate(inspector); 39 | } 40 | 41 | @Override public String toString() { 42 | return "SelfValidating(" + Types.typeToString(type) + ")"; 43 | } 44 | }; 45 | } 46 | return null; 47 | } 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/StandardValidators.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Type; 5 | import java.util.Set; 6 | 7 | final class StandardValidators { 8 | 9 | public static final Validator.Factory FACTORY = new Validator.Factory() { 10 | @Override public Validator create(Type type, 11 | Set annotations, 12 | Inspector inspector) { 13 | if (!annotations.isEmpty()) return null; 14 | if (type == boolean.class) return NO_OP_VALIDATOR; 15 | if (type == byte.class) return NO_OP_VALIDATOR; 16 | if (type == char.class) return NO_OP_VALIDATOR; 17 | if (type == double.class) return NO_OP_VALIDATOR; 18 | if (type == float.class) return NO_OP_VALIDATOR; 19 | if (type == int.class) return NO_OP_VALIDATOR; 20 | if (type == long.class) return NO_OP_VALIDATOR; 21 | if (type == short.class) return NO_OP_VALIDATOR; 22 | if (type == Boolean.class) return NO_OP_VALIDATOR; 23 | if (type == Byte.class) return NO_OP_VALIDATOR; 24 | if (type == Character.class) return NO_OP_VALIDATOR; 25 | if (type == Double.class) return NO_OP_VALIDATOR; 26 | if (type == Float.class) return NO_OP_VALIDATOR; 27 | if (type == Integer.class) return NO_OP_VALIDATOR; 28 | if (type == Long.class) return NO_OP_VALIDATOR; 29 | if (type == Short.class) return NO_OP_VALIDATOR; 30 | if (type == String.class) return NO_OP_VALIDATOR; 31 | if (type == Object.class) return NO_OP_VALIDATOR; 32 | return null; 33 | } 34 | }; 35 | 36 | @SuppressWarnings("WeakerAccess") // Synthetic accessor 37 | static final Validator NO_OP_VALIDATOR = new Validator() { 38 | @Override public void validate(Object validationTarget) throws ValidationException { 39 | // Nothing to do 40 | } 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/Util.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.AnnotatedElement; 5 | import java.lang.reflect.Type; 6 | import java.util.Collections; 7 | import java.util.LinkedHashSet; 8 | import java.util.Set; 9 | 10 | final class Util { 11 | 12 | public static final Set NO_ANNOTATIONS = Collections.emptySet(); 13 | 14 | private Util() { 15 | } 16 | 17 | public static boolean typesMatch(Type pattern, Type candidate) { 18 | // TODO: permit raw types (like Set.class) to match non-raw candidates (like Set). 19 | return pattern.equals(candidate); 20 | } 21 | 22 | public static Set validationAnnotations(AnnotatedElement annotatedElement) { 23 | return validationAnnotations(annotatedElement.getAnnotations()); 24 | } 25 | 26 | public static Set validationAnnotations(Annotation[] annotations) { 27 | Set result = null; 28 | for (Annotation annotation : annotations) { 29 | if (annotation.annotationType().isAnnotationPresent(ValidationQualifier.class)) { 30 | if (result == null) result = new LinkedHashSet<>(); 31 | result.add(annotation); 32 | } 33 | } 34 | return result != null ? Collections.unmodifiableSet(result) : Util.NO_ANNOTATIONS; 35 | } 36 | 37 | public static boolean isAnnotationPresent( 38 | Set annotations, Class annotationClass) { 39 | if (annotations.isEmpty()) return false; // Save an iterator in the common case. 40 | for (Annotation annotation : annotations) { 41 | if (annotation.annotationType() == annotationClass) return true; 42 | } 43 | return false; 44 | } 45 | 46 | /** Returns true if {@code annotations} has any annotation whose simple name is Nullable. */ 47 | public static boolean hasNullable(Annotation[] annotations) { 48 | for (Annotation annotation : annotations) { 49 | if (annotation.annotationType().getSimpleName().equals("Nullable")) { 50 | return true; 51 | } 52 | } 53 | return false; 54 | } 55 | 56 | /** Returns true if {@code annotations} has any annotation whose simple name is Nullable. */ 57 | public static boolean hasNullable(Set annotations) { 58 | for (Annotation annotation : annotations) { 59 | if (annotation.annotationType().getSimpleName().equals("Nullable")) { 60 | return true; 61 | } 62 | } 63 | return false; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/Validate.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface Validate { 11 | } 12 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/ValidatedBy.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.METHOD; 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | @Retention(RUNTIME) @Target(METHOD) public @interface ValidatedBy { 10 | /** 11 | * @return an array of one or more {@link Validator} classes. 12 | */ 13 | Class>[] value(); 14 | } 15 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/ValidationException.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | /** 4 | * Base class for validation exceptions. You can use this directly with your own message if you 5 | * want, or subclass it to put your own information. 6 | */ 7 | public class ValidationException extends RuntimeException { 8 | public ValidationException() { 9 | } 10 | 11 | public ValidationException(String message) { 12 | super(message); 13 | } 14 | 15 | public ValidationException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public ValidationException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | public ValidationException(String message, 24 | Throwable cause, 25 | boolean enableSuppression, 26 | boolean writableStackTrace) { 27 | super(message, cause, enableSuppression, writableStackTrace); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/ValidationQualifier.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** Annotates another annotation, causing it to specialize how values are validated. */ 11 | @Target(ANNOTATION_TYPE) 12 | @Retention(RUNTIME) 13 | @Documented 14 | public @interface ValidationQualifier { 15 | } 16 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/Validator.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Type; 5 | import java.util.Set; 6 | import javax.annotation.Nullable; 7 | 8 | /** 9 | * Validates objects of a given type. 10 | * 11 | * @param the type to validate. 12 | */ 13 | public abstract class Validator { 14 | 15 | /** 16 | * Validates a given {@code t} instance 17 | * 18 | * @param t the instance 19 | * @throws ValidationException upon invalidation 20 | */ 21 | public abstract void validate(T t) throws ValidationException; 22 | 23 | public final boolean isValid(T validationTarget) { 24 | try { 25 | validate(validationTarget); 26 | return true; 27 | } catch (ValidationException e) { 28 | return false; 29 | } 30 | } 31 | 32 | /** 33 | * @return a nullsafe validator that ignores null instances. 34 | */ 35 | public Validator nullSafe() { 36 | final Validator delegate = this; 37 | return new Validator() { 38 | @Override public void validate(T validationTarget) throws ValidationException { 39 | if (validationTarget != null) { 40 | delegate.validate(validationTarget); 41 | } 42 | } 43 | 44 | @Override public String toString() { 45 | return delegate + ".nullSafe()"; 46 | } 47 | }; 48 | } 49 | 50 | public interface Factory { 51 | /** 52 | * Attempts to create an adapter for {@code type} annotated with {@code annotations}. This 53 | * returns the adapter if one was created, or null if this factory isn't capable of creating 54 | * such an adapter. 55 | * 56 | *

Implementations may use to {@link Inspector#validator} to compose adapters of other types, 57 | * or {@link Inspector#nextValidator} to delegate to the underlying adapter of the same type. 58 | */ 59 | @Nullable Validator create(Type type, 60 | Set annotations, 61 | Inspector inspector); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /inspector/src/main/java/io/sweers/inspector/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Zac Sweers 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Inspector is a tiny class validation library. 19 | */ 20 | @com.uber.javaxextras.FieldsMethodsAndParametersAreNonNullByDefault 21 | package io.sweers.inspector; 22 | -------------------------------------------------------------------------------- /inspector/src/test/java/io/sweers/inspector/CompositeValidatorTest.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import org.junit.Test; 4 | 5 | import static com.google.common.truth.Truth.assertThat; 6 | 7 | public final class CompositeValidatorTest { 8 | 9 | private final Validator concreteValidator = 10 | CompositeValidator.of(new GreaterThan5Validator(), 11 | new LessThan10Validator(), 12 | new PositiveValidator()); 13 | 14 | private final Validator reflectiveValidator = new Inspector.Builder().build() 15 | .validator(Foo.class); 16 | 17 | @Test public void concrete_valid() { 18 | try { 19 | concreteValidator.validate(5); 20 | concreteValidator.validate(6); 21 | concreteValidator.validate(7); 22 | concreteValidator.validate(8); 23 | concreteValidator.validate(9); 24 | concreteValidator.validate(10); 25 | } catch (ValidationException e) { 26 | throw new AssertionError("This should be valid!"); 27 | } 28 | } 29 | 30 | @Test public void concrete_invalid() { 31 | try { 32 | concreteValidator.validate(4); 33 | throw new AssertionError("This should be invalid!"); 34 | } catch (ValidationException e) { 35 | assertThat(e).hasMessageThat() 36 | .isEqualTo("Was less than 5"); 37 | } 38 | 39 | try { 40 | concreteValidator.validate(11); 41 | throw new AssertionError("This should be invalid!"); 42 | } catch (ValidationException e) { 43 | assertThat(e).hasMessageThat() 44 | .isEqualTo("Was greater than 10"); 45 | } 46 | } 47 | 48 | @Test public void concrete_compositeValidation() { 49 | try { 50 | concreteValidator.validate(-1); 51 | throw new AssertionError("This should be invalid!"); 52 | } catch (ValidationException e) { 53 | assertThat(e).isInstanceOf(CompositeValidationException.class); 54 | CompositeValidationException ce = (CompositeValidationException) e; 55 | assertThat(ce.getExceptions()).hasSize(2); 56 | assertThat(e).hasMessageThat() 57 | .contains("Was less than 5"); 58 | assertThat(e).hasMessageThat() 59 | .contains("Was negative"); 60 | } 61 | } 62 | 63 | @Test public void reflective_valid() { 64 | try { 65 | reflectiveValidator.validate(new Foo(5)); 66 | reflectiveValidator.validate(new Foo(6)); 67 | reflectiveValidator.validate(new Foo(7)); 68 | reflectiveValidator.validate(new Foo(8)); 69 | reflectiveValidator.validate(new Foo(9)); 70 | reflectiveValidator.validate(new Foo(10)); 71 | } catch (ValidationException e) { 72 | throw new AssertionError("This should be valid!"); 73 | } 74 | } 75 | 76 | @Test public void reflective_invalid() { 77 | try { 78 | reflectiveValidator.validate(new Foo(4)); 79 | throw new AssertionError("This should be invalid!"); 80 | } catch (ValidationException e) { 81 | assertThat(e).hasMessageThat() 82 | .isEqualTo("Was less than 5"); 83 | } 84 | 85 | try { 86 | reflectiveValidator.validate(new Foo(11)); 87 | throw new AssertionError("This should be invalid!"); 88 | } catch (ValidationException e) { 89 | assertThat(e).hasMessageThat() 90 | .isEqualTo("Was greater than 10"); 91 | } 92 | } 93 | 94 | @Test public void reflective_compositeValidation() { 95 | try { 96 | reflectiveValidator.validate(new Foo(-1)); 97 | throw new AssertionError("This should be invalid!"); 98 | } catch (ValidationException e) { 99 | assertThat(e).isInstanceOf(CompositeValidationException.class); 100 | CompositeValidationException ce = (CompositeValidationException) e; 101 | assertThat(ce.getExceptions()).hasSize(2); 102 | assertThat(e).hasMessageThat() 103 | .contains("Was less than 5"); 104 | assertThat(e).hasMessageThat() 105 | .contains("Was negative"); 106 | } 107 | } 108 | 109 | static class PositiveValidator extends Validator { 110 | @Override public void validate(Integer integer) throws ValidationException { 111 | if (integer < 0) { 112 | throw new ValidationException("Was negative"); 113 | } 114 | } 115 | } 116 | 117 | static class GreaterThan5Validator extends Validator { 118 | @Override public void validate(Integer integer) throws ValidationException { 119 | if (integer < 5) { 120 | throw new ValidationException("Was less than 5"); 121 | } 122 | } 123 | } 124 | 125 | static class LessThan10Validator extends Validator { 126 | @Override public void validate(Integer integer) throws ValidationException { 127 | if (integer > 10) { 128 | throw new ValidationException("Was greater than 10"); 129 | } 130 | } 131 | } 132 | 133 | static class Foo { 134 | 135 | final int value; 136 | 137 | public Foo(int value) { 138 | this.value = value; 139 | } 140 | 141 | @ValidatedBy({ 142 | GreaterThan5Validator.class, LessThan10Validator.class, PositiveValidator.class 143 | }) 144 | public int value() { 145 | return value; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /inspector/src/test/java/io/sweers/inspector/InspectorTest.java: -------------------------------------------------------------------------------- 1 | package io.sweers.inspector; 2 | 3 | import io.sweers.inspector.InspectorTest.SelfValidatingType.NestedInheritedSelfValidating; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import org.junit.Test; 7 | 8 | import static com.google.common.truth.Truth.assertThat; 9 | import static io.sweers.inspector.InspectorTest.SelfValidatingType.VALIDATION_MESSAGE; 10 | import static junit.framework.TestCase.fail; 11 | 12 | public final class InspectorTest { 13 | 14 | @Test 15 | public void test() { 16 | Inspector inspector = new Inspector.Builder() 17 | .build(); 18 | 19 | Data data = new Data(); 20 | try { 21 | inspector.validator(Data.class).validate(data); 22 | fail("No validation was run"); 23 | } catch (ValidationException e) { 24 | assertThat(e).hasMessageThat().contains("thing() was null"); 25 | } 26 | 27 | List dataList = Arrays.asList(new Data()); 28 | try { 29 | inspector.validator(Types.newParameterizedType(List.class, Data.class)).validate(dataList); 30 | fail("No validation was run"); 31 | } catch (ValidationException e) { 32 | assertThat(e).hasMessageThat().contains("thing() was null"); 33 | } 34 | } 35 | 36 | public static class Data { 37 | public String thing() { 38 | return null; 39 | } 40 | } 41 | 42 | @Test public void testSelfValidating() { 43 | Inspector inspector = new Inspector.Builder() 44 | .build(); 45 | 46 | SelfValidatingType selfValidatingType = new SelfValidatingType(); 47 | 48 | try { 49 | inspector.validator(SelfValidatingType.class).validate(selfValidatingType); 50 | fail("This should throw a validation exception!"); 51 | } catch (ValidationException e) { 52 | assertThat(e).hasMessageThat().isEqualTo(VALIDATION_MESSAGE); 53 | } 54 | 55 | NestedInheritedSelfValidating nested = new NestedInheritedSelfValidating(); 56 | 57 | try { 58 | inspector.validator(NestedInheritedSelfValidating.class).validate(nested); 59 | fail("This should throw a validation exception!"); 60 | } catch (ValidationException e) { 61 | assertThat(e).hasMessageThat().isEqualTo(VALIDATION_MESSAGE); 62 | } 63 | } 64 | 65 | interface InheritedSelfValidating extends SelfValidating { 66 | 67 | } 68 | 69 | static class SelfValidatingType implements InheritedSelfValidating { 70 | static final String VALIDATION_MESSAGE = "This should fail!"; 71 | @Override public void validate(Inspector inspector) throws ValidationException { 72 | throw new ValidationException(VALIDATION_MESSAGE); 73 | } 74 | 75 | static class NestedInheritedSelfValidating extends SelfValidatingType { 76 | 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | apply from: file('gradle/dependencies.gradle') 3 | resolutionStrategy { 4 | eachPlugin { 5 | switch (requested.id.id) { 6 | case 'org.jetbrains.dokka': 7 | useModule("org.jetbrains.dokka:dokka-gradle-plugin:${deps.versions.dokka}") 8 | break 9 | case 'org.jetbrains.dokka-android': 10 | useModule("org.jetbrains.dokka:dokka-android-gradle-plugin:${deps.versions.dokka}") 11 | break 12 | case 'com.android.application': 13 | useModule(deps.build.gradlePlugins.android) 14 | break 15 | case 'com.android.library': 16 | useModule(deps.build.gradlePlugins.android) 17 | break 18 | case 'org.jetbrains.kotlin.jvm': 19 | useModule(deps.build.gradlePlugins.kotlin) 20 | break 21 | case 'org.jetbrains.kotlin.android': 22 | useModule(deps.build.gradlePlugins.kotlin) 23 | break 24 | case 'net.ltgt.errorprone': 25 | useVersion(deps.versions.errorPronePlugin) 26 | break 27 | case 'net.ltgt.apt-idea': 28 | useVersion(deps.versions.aptPlugin) 29 | break 30 | } 31 | } 32 | } 33 | repositories { 34 | jcenter() 35 | google() 36 | maven { 37 | url deps.build.repositories.kotlinEap 38 | } 39 | gradlePluginPortal() 40 | } 41 | } 42 | 43 | rootProject.name = 'inspector-root' 44 | include ':compiler-extensions:inspector-android-compiler-extension' 45 | include ':compiler-extensions:inspector-autovalue-compiler-extension' 46 | include ':compiler-extensions:inspector-nullability-compiler-extension' 47 | include ':compiler-extensions:inspector-rave-compiler-extension' 48 | include ':inspector' 49 | include ':inspector-compiler' 50 | include ':inspector-compiler-annotations' 51 | include ':inspector-compiler-extensions-api' 52 | include ':inspector-factory-compiler' 53 | include ':inspector-factory-compiler-annotations' 54 | include ':inspector-retrofit' 55 | include ':inspector-sample' 56 | include ':inspector-sample-android' 57 | 58 | --------------------------------------------------------------------------------