├── .gitignore ├── .travis.yml ├── LICENSE ├── ParcelablePlease-intellij-plugin ├── META-INF │ └── plugin.xml ├── ParcelablePlease-intellij-plugin.jar ├── images │ ├── generate.png │ └── intellij-plugin.png ├── resources │ └── META-INF │ │ └── plugin.xml └── src │ └── com │ └── hannesdorfmann │ └── parcelableplease │ └── plugin │ ├── CodeGenerator.java │ └── ParcelablePleaseAction.java ├── README.md ├── annotation ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── hannesdorfmann │ └── parcelableplease │ ├── ParcelBagger.java │ └── annotation │ ├── Bagger.java │ ├── ParcelableNoThanks.java │ ├── ParcelablePlease.java │ └── ParcelableThisPlease.java ├── checkstyle.xml ├── pom.xml ├── processor ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ ├── com │ │ │ └── hannesdorfmann │ │ │ │ └── parcelableplease │ │ │ │ └── processor │ │ │ │ ├── ParcelableField.java │ │ │ │ ├── ParcelablePleaseProcessor.java │ │ │ │ ├── ProcessorMessage.java │ │ │ │ ├── codegenerator │ │ │ │ ├── AbsCodeGen.java │ │ │ │ ├── BaggerCodeGen.java │ │ │ │ ├── CodeGenerator.java │ │ │ │ ├── FieldCodeGen.java │ │ │ │ ├── SupportedTypes.java │ │ │ │ ├── android │ │ │ │ │ ├── BundleCodeGen.java │ │ │ │ │ ├── ParcelableCodeGen.java │ │ │ │ │ ├── SparseArrayCodeGen.java │ │ │ │ │ └── SparseBooleanCodeGen.java │ │ │ │ ├── collection │ │ │ │ │ ├── AbsListCodeGen.java │ │ │ │ │ ├── ParcelableArrayCodeGen.java │ │ │ │ │ ├── PrimitiveArrayCodeGen.java │ │ │ │ │ └── StringListCodeGen.java │ │ │ │ ├── other │ │ │ │ │ ├── DateCodeGen.java │ │ │ │ │ └── SerializeableCodeGen.java │ │ │ │ ├── primitives │ │ │ │ │ └── BooleanCodeGen.java │ │ │ │ └── primitiveswrapper │ │ │ │ │ ├── AbsPrimitiveWrapperCodeGen.java │ │ │ │ │ └── BooleanWrapperCodeGen.java │ │ │ │ └── util │ │ │ │ ├── CodeGenInfo.java │ │ │ │ └── TypeUtils.java │ │ └── repacked │ │ │ └── com │ │ │ └── squareup │ │ │ └── javawriter │ │ │ └── JavaWriter.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── javax.annotation.processing.Processor │ └── test │ └── java │ └── com │ └── hannesdorfmann │ └── parcelableplease │ └── processor │ └── ParcelablePleaseProcessorTest.java └── sample ├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── hannesdorfmann │ │ └── parcelableplease │ │ └── sample │ │ ├── MainActivity.java │ │ └── model │ │ ├── Banana.java │ │ ├── Fruit.java │ │ ├── FruitType.java │ │ ├── FruitTypeBagger.java │ │ └── Orange.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ └── activity_main.xml │ ├── menu │ └── menu_main.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .factorypath 3 | .project 4 | .settings 5 | eclipsebin 6 | 7 | bin 8 | gen 9 | build 10 | out 11 | lib 12 | 13 | target 14 | pom.xml.* 15 | release.properties 16 | log 17 | settings.xml 18 | 19 | .idea 20 | *.iml 21 | *.iws 22 | classes 23 | 24 | obj 25 | 26 | .DS_Store 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk7 5 | - oraclejdk8 6 | 7 | #env: 8 | # global: 9 | # - secure: "TM2N/yo3azbA7+J6WG0W1RmK4vvS7b4JzvJP8NcXpoZCQw2Je1nQqmKS3F/04/DEUf8+SKVh2thKEeUxLN8knCO0OQLxeKIFj9PLvtgHnV8beB+pPxUdpEvMlB6ISeDsOZ098zoax28zfMwR7+uMeGe2VbtTSpwtqt4jkWx3ooA=" 10 | # - secure: "A5flBI/PCmmxuHjqG73CIXqHYg8X1GWloA3jv0zrhCAuK5m3nAvt7JY2qom22ttEY9JI32dlArxhusrKib18gKNgZbxzoN5PydkG6T0uuGZw+uZM6jPiTlvkC0F4ikL6lg1dBap4BoakDMwAPfb11UpETamBGKOWdopLqw6T6w0=" 11 | 12 | branches: 13 | except: 14 | - gh-pages 15 | 16 | notifications: 17 | email: false 18 | 19 | sudo: false 20 | 21 | cache: 22 | directories: 23 | - $HOME/.m2 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014 Hannes Dorfmann 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /ParcelablePlease-intellij-plugin/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.hannesdorfmann.parcelableplease.plugin 3 | ParcelablePlease 4 | 1.0.2 5 | Hannes Dorfmann 6 | 7 | ParcelablePlease on github 10 | ]]> 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 24 | 25 | com.intellij.modules.java 26 | com.intellij.modules.androidstudio 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ParcelablePlease-intellij-plugin/ParcelablePlease-intellij-plugin.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockeqwe/ParcelablePlease/4bcb6695ade8cb0ec60af5b137b60f751262086c/ParcelablePlease-intellij-plugin/ParcelablePlease-intellij-plugin.jar -------------------------------------------------------------------------------- /ParcelablePlease-intellij-plugin/images/generate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockeqwe/ParcelablePlease/4bcb6695ade8cb0ec60af5b137b60f751262086c/ParcelablePlease-intellij-plugin/images/generate.png -------------------------------------------------------------------------------- /ParcelablePlease-intellij-plugin/images/intellij-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockeqwe/ParcelablePlease/4bcb6695ade8cb0ec60af5b137b60f751262086c/ParcelablePlease-intellij-plugin/images/intellij-plugin.png -------------------------------------------------------------------------------- /ParcelablePlease-intellij-plugin/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.hannesdorfmann.parcelableplease.plugin 3 | ParcelablePlease 4 | 1.0.2 5 | Hannes Dorfmann 6 | 7 | ParcelablePlease on github 10 | ]]> 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 24 | 25 | com.intellij.modules.java 26 | com.intellij.modules.androidstudio 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ParcelablePlease-intellij-plugin/src/com/hannesdorfmann/parcelableplease/plugin/CodeGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Hannes Dormfann (www.hannesdorfmann.com) 3 | * 4 | * 5 | * Parts of this code are taken from Michał Charmas. 6 | * 7 | * Copyright (C) 2013 Michał Charmas (http://blog.charmas.pl) 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | 23 | package com.hannesdorfmann.parcelableplease.plugin; 24 | 25 | import com.intellij.codeInsight.AnnotationUtil; 26 | import com.intellij.psi.JavaPsiFacade; 27 | import com.intellij.psi.PsiClass; 28 | import com.intellij.psi.PsiClassType; 29 | import com.intellij.psi.PsiElementFactory; 30 | import com.intellij.psi.PsiField; 31 | import com.intellij.psi.PsiFile; 32 | import com.intellij.psi.PsiImportList; 33 | import com.intellij.psi.PsiImportStatementBase; 34 | import com.intellij.psi.PsiJavaCodeReferenceElement; 35 | import com.intellij.psi.PsiJavaFile; 36 | import com.intellij.psi.PsiMethod; 37 | import com.intellij.psi.PsiParameter; 38 | import com.intellij.psi.PsiParameterList; 39 | import com.intellij.psi.PsiReferenceList; 40 | import com.intellij.psi.codeStyle.JavaCodeStyleManager; 41 | 42 | /** 43 | * Generates the real code for ParcelablePlease that will be injected in intellij code editor 44 | * window 45 | * 46 | * @author Hannes Dorfmann 47 | */ 48 | public class CodeGenerator { 49 | 50 | private static final String PARCELABLE_PLEASE_SUFFIX = "ParcelablePlease"; 51 | 52 | private static final String ANNOTATION_PACKAGE = 53 | "com.hannesdorfmann.parcelableplease.annotation"; 54 | 55 | private static final String ANNOTATION_NAME = "ParcelablePlease"; 56 | 57 | private PsiClass psiClass; 58 | 59 | public CodeGenerator(PsiClass psiClass) { 60 | this.psiClass = psiClass; 61 | } 62 | 63 | private String generateCreator() { 64 | 65 | String className = psiClass.getName(); 66 | String parcelablePleaseClass = getParcelablePleaseClassName(); 67 | 68 | StringBuilder sb = new StringBuilder("public static final android.os.Parcelable.Creator<"); 69 | sb.append(className) 70 | .append("> CREATOR = new android.os.Parcelable.Creator<") 71 | .append(className) 72 | .append(">(){") 73 | .append("public ") 74 | .append(className) 75 | .append(" createFromParcel(android.os.Parcel source) {") 76 | .append(className) 77 | .append(" target = new ") 78 | .append(className) 79 | .append("();") 80 | .append(parcelablePleaseClass) 81 | .append(".readFromParcel(target, source); return target;}") 82 | .append("public ") 83 | .append(className) 84 | .append("[] newArray(int size) {") 85 | .append("return new ") 86 | .append(className) 87 | .append("[size];}") 88 | .append("};"); 89 | 90 | return sb.toString(); 91 | } 92 | 93 | private String generateWriteToParcel() { 94 | 95 | String parcelablePleaseClass = getParcelablePleaseClassName(); 96 | 97 | StringBuilder sb = new StringBuilder( 98 | "@Override public void writeToParcel(android.os.Parcel dest, int flags) {"); 99 | 100 | sb.append(parcelablePleaseClass).append(".writeToParcel(this, dest, flags);").append("}"); 101 | 102 | return sb.toString(); 103 | } 104 | 105 | private String generateDescribeContents() { 106 | return "@Override public int describeContents() { return 0; }"; 107 | } 108 | 109 | private void addImport(PsiElementFactory elementFactory, String fullyQualifiedName){ 110 | final PsiFile file = psiClass.getContainingFile(); 111 | if (!(file instanceof PsiJavaFile)) { 112 | return; 113 | } 114 | final PsiJavaFile javaFile = (PsiJavaFile)file; 115 | 116 | final PsiImportList importList = javaFile.getImportList(); 117 | if (importList == null) { 118 | return; 119 | } 120 | 121 | // Check if already imported 122 | for (PsiImportStatementBase is : importList.getAllImportStatements()) { 123 | String impQualifiedName = is.getImportReference().getQualifiedName(); 124 | if (fullyQualifiedName.equals(impQualifiedName)){ 125 | return; // Already imported so nothing neede 126 | } 127 | 128 | } 129 | 130 | // Not imported yet so add it 131 | importList.add(elementFactory.createImportStatementOnDemand(fullyQualifiedName)); 132 | } 133 | 134 | 135 | public void clearPrevious(){ 136 | 137 | // Delete creator 138 | PsiField creatorField = psiClass.findFieldByName("CREATOR", false); 139 | if (creatorField != null) { 140 | creatorField.delete(); 141 | } 142 | 143 | // Delete Methods 144 | findAndRemoveMethod("describeContents"); 145 | findAndRemoveMethod("writeToParcel", "android.os.Parcel", "int"); 146 | 147 | } 148 | 149 | /** 150 | * Finds and removes a given method 151 | * @param methodName 152 | * @param arguments 153 | */ 154 | private void findAndRemoveMethod(String methodName, String... arguments) { 155 | // Maybe there's an easier way to do this with mClass.findMethodBySignature(), but I'm not an expert on Psi* 156 | PsiMethod[] methods = psiClass.findMethodsByName(methodName, false); 157 | 158 | for (PsiMethod method : methods) { 159 | PsiParameterList parameterList = method.getParameterList(); 160 | 161 | if (parameterList.getParametersCount() == arguments.length) { 162 | boolean shouldDelete = true; 163 | 164 | PsiParameter[] parameters = parameterList.getParameters(); 165 | 166 | for (int i = 0; i < arguments.length; i++) { 167 | if (!parameters[i].getType().getCanonicalText().equals(arguments[i])) { 168 | shouldDelete = false; 169 | } 170 | } 171 | 172 | if (shouldDelete) { 173 | method.delete(); 174 | } 175 | } 176 | } 177 | } 178 | 179 | /** 180 | * Generate and insert the Parcel and ParcelablePlease code 181 | */ 182 | public void generate() { 183 | 184 | PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(psiClass.getProject()); 185 | JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(psiClass.getProject()); 186 | 187 | // Clear any previous 188 | clearPrevious(); 189 | 190 | // Implements parcelable 191 | makeClassImplementParcelable(elementFactory, styleManager); 192 | 193 | // @ParcelablePlease Annotation 194 | addAnnotation(elementFactory, styleManager); 195 | 196 | // Creator 197 | PsiField creatorField = elementFactory.createFieldFromText(generateCreator(), psiClass); 198 | 199 | // Describe contents method 200 | PsiMethod describeContentsMethod = 201 | elementFactory.createMethodFromText(generateDescribeContents(), psiClass); 202 | 203 | // Method for writing to the parcel 204 | PsiMethod writeToParcelMethod = 205 | elementFactory.createMethodFromText(generateWriteToParcel(), psiClass); 206 | 207 | styleManager.shortenClassReferences( 208 | psiClass.addBefore(describeContentsMethod, psiClass.getLastChild())); 209 | 210 | styleManager.shortenClassReferences( 211 | psiClass.addBefore(writeToParcelMethod, psiClass.getLastChild())); 212 | 213 | styleManager.shortenClassReferences(psiClass.addBefore(creatorField, psiClass.getLastChild())); 214 | } 215 | 216 | /** 217 | * Add the @Parcelable annotation if not already annotated 218 | */ 219 | private void addAnnotation(PsiElementFactory elementFactory, JavaCodeStyleManager styleManager) { 220 | 221 | boolean annotated = AnnotationUtil.isAnnotated(psiClass, ANNOTATION_PACKAGE+"."+ANNOTATION_NAME, false); 222 | 223 | if (!annotated) { 224 | styleManager.shortenClassReferences(psiClass.getModifierList().addAnnotation( 225 | ANNOTATION_NAME)); 226 | } 227 | } 228 | 229 | /** 230 | * Make the class implementing Parcelable 231 | */ 232 | private void makeClassImplementParcelable(PsiElementFactory elementFactory, JavaCodeStyleManager styleManager) { 233 | final PsiClassType[] implementsListTypes = psiClass.getImplementsListTypes(); 234 | final String implementsType = "android.os.Parcelable"; 235 | 236 | for (PsiClassType implementsListType : implementsListTypes) { 237 | PsiClass resolved = implementsListType.resolve(); 238 | 239 | // Already implements Parcelable, no need to add it 240 | if (resolved != null && implementsType.equals(resolved.getQualifiedName())) { 241 | return; 242 | } 243 | } 244 | 245 | PsiJavaCodeReferenceElement implementsReference = 246 | elementFactory.createReferenceFromText(implementsType, psiClass); 247 | PsiReferenceList implementsList = psiClass.getImplementsList(); 248 | 249 | if (implementsList != null) { 250 | styleManager.shortenClassReferences(implementsList.add(implementsReference)); 251 | } 252 | } 253 | 254 | private String getParcelablePleaseClassName() { 255 | String className = psiClass.getName(); 256 | StringBuilder classNameBuilder = new StringBuilder(); 257 | classNameBuilder.append(className); 258 | classNameBuilder.append(PARCELABLE_PLEASE_SUFFIX); 259 | PsiClass containingClass = psiClass.getContainingClass(); 260 | while (containingClass != null) { 261 | classNameBuilder.insert(0, '$'); 262 | classNameBuilder.insert(0, containingClass.getName()); 263 | containingClass = containingClass.getContainingClass(); 264 | } 265 | 266 | return classNameBuilder.toString(); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /ParcelablePlease-intellij-plugin/src/com/hannesdorfmann/parcelableplease/plugin/ParcelablePleaseAction.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.plugin; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.LangDataKeys; 6 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 7 | import com.intellij.openapi.command.WriteCommandAction; 8 | import com.intellij.openapi.editor.Editor; 9 | import com.intellij.psi.PsiClass; 10 | import com.intellij.psi.PsiElement; 11 | import com.intellij.psi.PsiFile; 12 | import com.intellij.psi.util.PsiTreeUtil; 13 | /** 14 | * A simple plugin for generating the boilerplateCode of ParcelablePlease 15 | * 16 | * @author Hannes Dorfmann 17 | */ 18 | public class ParcelablePleaseAction extends AnAction { 19 | public void actionPerformed(AnActionEvent e) { 20 | 21 | final PsiClass psiClass = getPsiClassFromContext(e); 22 | 23 | // Generate Code 24 | new WriteCommandAction.Simple(psiClass.getProject(), psiClass.getContainingFile()) { 25 | @Override 26 | protected void run() throws Throwable { 27 | new CodeGenerator(psiClass).generate(); 28 | } 29 | }.execute(); 30 | } 31 | 32 | @Override 33 | public void update(AnActionEvent e) { 34 | PsiClass psiClass = getPsiClassFromContext(e); 35 | e.getPresentation().setEnabled(psiClass != null && !psiClass.isEnum() && !psiClass.isInterface()); 36 | } 37 | 38 | 39 | /** 40 | * Get the class where currently the curser is 41 | */ 42 | private PsiClass getPsiClassFromContext(AnActionEvent e) { 43 | PsiFile psiFile = e.getData(LangDataKeys.PSI_FILE); 44 | Editor editor = e.getData(PlatformDataKeys.EDITOR); 45 | 46 | 47 | if (psiFile == null || editor == null) { 48 | return null; 49 | } 50 | 51 | int offset = editor.getCaretModel().getOffset(); 52 | PsiElement element = psiFile.findElementAt(offset); 53 | 54 | return PsiTreeUtil.getParentOfType(element, PsiClass.class); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ParcelablePlease 2 | An AnnotationProcessor for generating Android Parcelable boilerplate code. [See this blog entry](http://hannesdorfmann.com/android/ParcelablePlease) for comparison with other parcel libraries. 3 | 4 | # Dependency 5 | Latest version: [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.hannesdorfmann.parcelableplease/annotation/badge.png)](https://maven-badges.herokuapp.com/maven-central/com.hannesdorfmann.parcelableplease/annotation) 6 | 7 | ```groovy 8 | compile 'com.hannesdorfmann.parcelableplease:annotation:x.x.x' 9 | apt 'com.hannesdorfmann.parcelableplease:processor:x.x.x' 10 | ``` 11 | In android studio you need to apply Hugo Visser's awesome [android-apt](https://bitbucket.org/hvisser/android-apt) gradle plugin to enable annotation processing. 12 | 13 | # How to use 14 | Simply annotate the classes you want to make Parcelable with `@ParcelablePlease` and implement the `Parcelable` as well as the `CREATOR` (This step can be automated by using the Android Studio plugin, see below). 15 | 16 | ```java 17 | @ParcelablePlease 18 | public class Model implements Parcelable { 19 | 20 | int id; 21 | String name; 22 | OtherModel otherModel; 23 | 24 | @Override public int describeContents() { 25 | return 0; 26 | } 27 | 28 | @Override public void writeToParcel(Parcel dest, int flags) { 29 | ModelParcelablePlease.writeToParcel(this, dest, flags); 30 | } 31 | 32 | public static final Creator CREATOR = new Creator() { 33 | public Model createFromParcel(Parcel source) { 34 | Model target = new Model(); 35 | ModelParcelablePlease.readFromParcel(target, source); 36 | return target; 37 | } 38 | 39 | public Model[] newArray(int size) { 40 | return new Model[size]; 41 | } 42 | }; 43 | } 44 | 45 | ``` 46 | The `ParcelablePlease` annotation processor will generate a class named `ClassName + ParcelablePlease` for you with all the code for writing and reading data in the Parcel. So from the example above: `ModelParcelablePlease` is generated and provides two static methods: `ModelParcelablePlease.readFromParcel(Model, Parcel)` and `ModelParcelablePlease.writeToParcel(Model, Parcel, int)` 47 | 48 | Once you have done this basic setup by connecting the generated code with your Model class you can change the model class, add fields, remove fields etc. without worring about `Parcelable` because `ParcelablePlease` will generate the code for you everytime you compile. 49 | 50 | # Android Studio Plugin 51 | Like mentioned above you have to write few lines of code to connect the Parcelable class with the generated code. Don't worry, you don't have to do this by hand. There is a Android Studio / IntelliJ plugin that can do that for you: 52 | 53 | 1. Open Android Studio / IntelliJ 54 | 2. Open the Preferences (on Mac with `⌘ + ;` ) 55 | 3. Type in the searchbox "plugin" to navigate quickly to the plugins section 56 | 4. Click on _Browse repositories..._ button 57 | 5. Search for _ParcelablePlease_ and install this plugin 58 | 6. Restart Android Studio 59 | 7. Create a Model class and open the Generate Menu (on Mac with ``⌘ + n` ). Note that the cursor must be somewhere in the code of the class. 60 | 61 | ![Preferences](https://github.com/sockeqwe/ParcelablePlease/raw/master/ParcelablePlease-intellij-plugin/images/generate.png "Preferences") 62 | 63 | 64 | Remember that you may have to compile your project to make Android Studio run annotation Processing which will generate the ParcelPlease classes. 65 | 66 | 67 | # Supported types 68 | 69 | - **Primitives** 70 | - byte 71 | - boolean 72 | - double 73 | - float 74 | - int 75 | - long 76 | - String 77 | 78 | - **Primitive wrappers** 79 | - Byte 80 | - Boolean 81 | - Double 82 | - Float 83 | - Int 84 | - Long 85 | 86 | - **Android specific** 87 | - Parcelable (anything that implements Parcelable) 88 | - Bundle 89 | - SparseBooleanArray 90 | 91 | - **Arrays** 92 | - int[] 93 | - long[] 94 | - double[] 95 | - String[] 96 | - float[] 97 | - char[] 98 | - boolean[] 99 | - byte[] 100 | - Parcelable[] - array of anything that implements Parcelable 101 | 102 | - **Other** 103 | - Serializable 104 | - java.util.Date (by simpling passing time as millis) 105 | 106 | 107 | - **Collections** 108 | - List 109 | - ArrayList 110 | - LinkedList 111 | - CopyOnWriteArrayList 112 | - List 113 | 114 | 115 | # Bagger 116 | Do you want to make a field Parcelable but it's not listed in the supported types list from above (i.e. `java.util.Map`)? No Problem: You can provide your own implementation implementing a `ParcelBagger` like this: 117 | ```java 118 | public class DateBagger implements ParcelBagger { 119 | 120 | @Override public void write(Date value, Parcel out, int flags) { 121 | if (value == null) { 122 | out.writeLong(-1); 123 | } else { 124 | out.writeLong(value.getTime()); 125 | } 126 | } 127 | 128 | @Override public Date read(Parcel in) { 129 | 130 | long timeMillis = in.readLong(); 131 | if (timeMillis == -1) { 132 | return null; 133 | } 134 | 135 | return new Date(timeMillis); 136 | } 137 | } 138 | ``` 139 | 140 | You can use your ParcelBagger with the `@Bagger` annotation like this: 141 | 142 | ```java 143 | @ParcelablePlease 144 | public class Person implements Parcelable { 145 | 146 | int id; 147 | String name; 148 | 149 | @Bagger(DateBagger.class) 150 | Date date; 151 | 152 | } 153 | ``` 154 | 155 | Remember that you have to take care about special cases like what if the value is null. 156 | **Note** that `java.util.Date` is already supported by `ParcelablePlease`. The example above is just to give you an idea of how a implementation could look like. 157 | 158 | 159 | # Configuration 160 | You can configure which fields should be serialized. There are two ways: 161 | 1. As default all class (and super classes) fields will be serialized. You can mark field's you don't want to serialize by annotating them with `@ParcelableNoThanks` 162 | 2. You can do the other way: You could change the settings to only serialize fields that are marked with `@ParcelableThisPlease ` like this: 163 | ```java 164 | @ParcelablePlease( allFields = false) 165 | public class Animal implements Parcelable { 166 | 167 | @ParcelableThisPlease 168 | String name; 169 | 170 | int age; // This will not be serialized 171 | 172 | } 173 | ``` 174 | 175 | 176 | As default `ParcelablePlease` will throw a compile error if it tries to serialize private fields (private fields are not supported because of visibility issues). If your class marked with `@ParcelablePlease` contains private fields you could mark them as not parcelable with `@ParcelableNoThanks` or you could cofigure ParcelablePlease to skip private fields by using `@ParcelablePlease( ignorePrivateFields = true)`: 177 | 178 | ```java 179 | @ParcelablePlease( ignorePrivateFields = true) 180 | public class Person implements Parcelable { 181 | 182 | String name; 183 | 184 | private int age; // No compile error 185 | 186 | } 187 | ``` 188 | 189 | # Limitations 190 | - **Fields** must have at least default (package) visibility. That means private fields are not supported. 191 | - **Private classes** are not supported because of visibilitiy issues 192 | -------------------------------------------------------------------------------- /annotation/.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .factorypath 3 | .project 4 | .settings 5 | eclipsebin 6 | 7 | bin 8 | gen 9 | build 10 | out 11 | lib 12 | 13 | target 14 | pom.xml.* 15 | release.properties 16 | log 17 | settings.xml 18 | 19 | .idea 20 | *.iml 21 | *.iws 22 | classes 23 | 24 | obj 25 | 26 | .DS_Store 27 | -------------------------------------------------------------------------------- /annotation/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | parent 7 | com.hannesdorfmann.parcelableplease 8 | 1.0.3-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | annotation 13 | 14 | ParcelablePlease Annotations 15 | 16 | 17 | 18 | com.google.android 19 | android 20 | provided 21 | 22 | 23 | 24 | junit 25 | junit 26 | test 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.apache.maven.plugins 35 | maven-javadoc-plugin 36 | 37 | 38 | attach-javadocs 39 | 40 | jar 41 | 42 | 43 | 44 | 45 | 46 | 47 | org.apache.maven.plugins 48 | maven-source-plugin 49 | 50 | 51 | attach-sources 52 | 53 | jar 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/hannesdorfmann/parcelableplease/ParcelBagger.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease; 2 | 3 | import android.os.Parcel; 4 | 5 | /** 6 | * A way to put a not Parcelable object into a Parcel and vice versa. 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | public interface ParcelBagger { 11 | 12 | /** 13 | * Write the value into the parcelable 14 | */ 15 | public void write(T value, Parcel out, int flags); 16 | 17 | /** 18 | * Read the value from parcelable 19 | */ 20 | public T read(Parcel in); 21 | } 22 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/hannesdorfmann/parcelableplease/annotation/Bagger.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.annotation; 2 | 3 | import com.hannesdorfmann.parcelableplease.ParcelBagger; 4 | 5 | /** 6 | * Annotate Fields you want to put in a Parcel which is not Parcelable. 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | public @interface Bagger { 11 | Class> value(); 12 | } 13 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/hannesdorfmann/parcelableplease/annotation/ParcelableNoThanks.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Annotate fields not to be parcelable (transient) 11 | * 12 | * @author Hannes Dorfmann 13 | */ 14 | @Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) @Documented 15 | public @interface ParcelableNoThanks { 16 | } 17 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/hannesdorfmann/parcelableplease/annotation/ParcelablePlease.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Annotate classes that have 11 | * 12 | * @author Hannes Dorfmann 13 | */ 14 | @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) @Documented 15 | public @interface ParcelablePlease { 16 | 17 | /** 18 | * Should all fields be parcelable. 19 | * Default is true. 20 | */ 21 | boolean allFields() default true; 22 | 23 | /** 24 | * Should private fields be ignored? 25 | * Otherwise an error while compiling will be thrown if the class annotated with this Annotation 26 | * contains private fields. 27 | */ 28 | boolean ignorePrivateFields() default false; 29 | } 30 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/hannesdorfmann/parcelableplease/annotation/ParcelableThisPlease.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Annotate fields with that if you don't want to use all 11 | * @author Hannes Dorfmann 12 | */ 13 | @Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) @Documented 14 | public @interface ParcelableThisPlease { 15 | } 16 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.sonatype.oss 9 | oss-parent 10 | 7 11 | 12 | 13 | com.hannesdorfmann.parcelableplease 14 | parent 15 | pom 16 | 1.0.3-SNAPSHOT 17 | 18 | ParcelablePlease Parent 19 | Generates Android Parcelable code 20 | https://github.com/sockeqwe/ParcelablePlease 21 | 22 | 23 | GitHub Issues 24 | http://github.com/sockeqwe/ParcelablePlease/issues 25 | 26 | 27 | 28 | 29 | Apache 2.0 30 | http://www.apache.org/licenses/LICENSE-2.0.txt 31 | 32 | 33 | 34 | 35 | Hannes Dorfmann 36 | http://www.hannesdorfmann.com 37 | 38 | 39 | 40 | 41 | annotation 42 | processor 43 | 44 | 45 | 46 | 47 | UTF-8 48 | 49 | 50 | 1.6 51 | 4.1.1.4 52 | 53 | 54 | 4.12 55 | 1.0-SNAPSHOT 56 | 1.0-SNAPSHOT 57 | 58 | 59 | 60 | 61 | 62 | ossrh 63 | https://oss.sonatype.org/content/repositories/snapshots 64 | 65 | 66 | ossrh 67 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | com.google.android 76 | android 77 | ${android.version} 78 | 79 | 80 | 81 | junit 82 | junit 83 | ${junit.version} 84 | 85 | 86 | com.google.testing.compile 87 | compile-testing 88 | ${compile-testing.version} 89 | 90 | 91 | com.google.truth 92 | truth 93 | ${truth.version} 94 | test 95 | 96 | 97 | 98 | 99 | 100 | 101 | autoincrement-versions-maven-plugin 102 | autoincrement-versions-maven-plugin 103 | http://autoincrement-versions-maven-plugin.googlecode.com/svn/repo 104 | 105 | true 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | org.apache.maven.plugins 115 | maven-invoker-plugin 116 | 1.7 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-compiler-plugin 122 | 3.1 123 | 124 | 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-compiler-plugin 131 | 132 | ${java.version} 133 | ${java.version} 134 | -Xlint:all 135 | true 136 | true 137 | 138 | 139 | 140 | 141 | 142 | org.apache.maven.plugins 143 | maven-javadoc-plugin 144 | 145 | 146 | attach-javadocs 147 | 148 | jar 149 | 150 | 151 | 152 | 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-source-plugin 157 | 158 | 159 | attach-sources 160 | 161 | jar 162 | 163 | 164 | 165 | 166 | 167 | 168 | org.apache.maven.plugins 169 | maven-release-plugin 170 | 2.4.2 171 | 172 | 173 | org.apache.maven.scm 174 | maven-scm-provider-gitexe 175 | 1.9 176 | 177 | 178 | 179 | true 180 | 181 | 182 | 183 | 184 | org.apache.maven.plugins 185 | maven-checkstyle-plugin 186 | 2.10 187 | 188 | false 189 | true 190 | checkstyle.xml 191 | 192 | 193 | 194 | compile 195 | 196 | checkstyle 197 | 198 | 199 | 200 | 201 | 202 | 230 | 231 | 251 | 252 | 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /processor/.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .factorypath 3 | .project 4 | .settings 5 | eclipsebin 6 | 7 | bin 8 | gen 9 | build 10 | out 11 | lib 12 | 13 | target 14 | pom.xml.* 15 | release.properties 16 | log 17 | settings.xml 18 | 19 | .idea 20 | *.iml 21 | *.iws 22 | classes 23 | 24 | obj 25 | 26 | .DS_Store 27 | -------------------------------------------------------------------------------- /processor/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | parent 7 | com.hannesdorfmann.parcelableplease 8 | 1.0.3-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | processor 13 | ParcelablePlease Processor 14 | 15 | 16 | 17 | ${project.groupId} 18 | annotation 19 | ${project.version} 20 | 21 | 22 | 23 | 24 | junit 25 | junit 26 | test 27 | 28 | 29 | 30 | com.google.testing.compile 31 | compile-testing 32 | test 33 | 34 | 35 | 36 | 37 | com.google.truth 38 | truth 39 | test 40 | 41 | 42 | com.google.android 43 | android 44 | test 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-compiler-plugin 55 | 56 | 57 | default-compile 58 | 59 | compile 60 | 61 | 62 | -proc:none 63 | 64 | 65 | 66 | default-test-compile 67 | 68 | testCompile 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-invoker-plugin 78 | 79 | true 80 | ${project.build.directory}/it 81 | 82 | */pom.xml 83 | 84 | ${project.build.directory}/local-repo 85 | verify 86 | 87 | ${project.version} 88 | ${project.groupId} 89 | 90 | 91 | 92 | 93 | integration-test 94 | 95 | install 96 | run 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-javadoc-plugin 106 | 107 | 108 | attach-javadocs 109 | 110 | jar 111 | 112 | 113 | 114 | 115 | 116 | 117 | org.apache.maven.plugins 118 | maven-source-plugin 119 | 120 | 121 | attach-sources 122 | 123 | jar 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/ParcelableField.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor; 2 | 3 | import com.hannesdorfmann.parcelableplease.ParcelBagger; 4 | import com.hannesdorfmann.parcelableplease.annotation.Bagger; 5 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.BaggerCodeGen; 6 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 7 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.SupportedTypes; 8 | import com.hannesdorfmann.parcelableplease.processor.util.CodeGenInfo; 9 | import java.lang.reflect.Constructor; 10 | import java.lang.reflect.Modifier; 11 | import java.util.List; 12 | import javax.lang.model.element.Element; 13 | import javax.lang.model.element.ElementKind; 14 | import javax.lang.model.element.ExecutableElement; 15 | import javax.lang.model.element.VariableElement; 16 | import javax.lang.model.type.DeclaredType; 17 | import javax.lang.model.type.MirroredTypeException; 18 | import javax.lang.model.type.TypeKind; 19 | import javax.lang.model.type.TypeMirror; 20 | import javax.lang.model.util.Elements; 21 | import javax.lang.model.util.Types; 22 | 23 | // TODO refactor this class in interfaces and sublacsses for BaggerAnnotated, normal and Generic fields 24 | 25 | /** 26 | * Scans a field and collects information for the java code generating process 27 | * 28 | * @author Hannes Dorfmann 29 | */ 30 | public class ParcelableField { 31 | 32 | private String fieldName; 33 | private String type; 34 | private Element element; 35 | private FieldCodeGen codeGenerator; 36 | private TypeMirror genericsTypeArgument; 37 | private String baggerFullyQualifiedName; 38 | 39 | public ParcelableField(VariableElement element, Elements elementUtils, Types typeUtils) { 40 | 41 | this.element = element; 42 | fieldName = element.getSimpleName().toString(); 43 | type = element.asType().toString(); 44 | 45 | // Check for Bagger 46 | Bagger baggerAnnotation = element.getAnnotation(Bagger.class); 47 | if (baggerAnnotation != null) { 48 | // has a bagger annotation 49 | try { 50 | Class clazz = baggerAnnotation.value(); 51 | baggerFullyQualifiedName = getFullQualifiedNameByClass(clazz); 52 | } catch (MirroredTypeException mte) { 53 | TypeMirror baggerClass = mte.getTypeMirror(); 54 | baggerFullyQualifiedName = getFullQualifiedNameByTypeMirror(baggerClass); 55 | } 56 | 57 | // Everything is fine, so use the bagger 58 | codeGenerator = new BaggerCodeGen(); 59 | } else { 60 | // Not using Bagger 61 | CodeGenInfo res = SupportedTypes.getCodeGenInfo(element, elementUtils, typeUtils); 62 | codeGenerator = res.getCodeGenerator(); 63 | genericsTypeArgument = res.getGenericsType(); 64 | 65 | // Check if type is supported 66 | if (codeGenerator == null) { 67 | ProcessorMessage.error(element, 68 | "Unsupported type %s for field %s. You could use @%s to provide your own serialization mechanism", 69 | element.asType().toString(), element.getSimpleName(), Bagger.class.getSimpleName()); 70 | } 71 | } 72 | } 73 | 74 | private String getFullQualifiedNameByClass(Class clazz) { 75 | 76 | // Check public 77 | if (!Modifier.isPublic(clazz.getModifiers())) { 78 | 79 | ProcessorMessage.error(element, "The %s must be a public class to be a valid Bagger", 80 | clazz.getCanonicalName()); 81 | return null; 82 | } 83 | 84 | // Check constructors 85 | Constructor[] constructors = clazz.getConstructors(); 86 | 87 | boolean foundDefaultConstructor = false; 88 | for (Constructor c : constructors) { 89 | boolean isPublicConstructor = Modifier.isPublic(c.getModifiers()); 90 | Class[] pType = c.getParameterTypes(); 91 | 92 | if (pType.length == 0 && isPublicConstructor) { 93 | foundDefaultConstructor = true; 94 | break; 95 | } 96 | } 97 | 98 | if (!foundDefaultConstructor) { 99 | ProcessorMessage.error(element, "The %s must provide a public empty default constructor", 100 | clazz.getCanonicalName()); 101 | return null; 102 | } 103 | 104 | return clazz.getCanonicalName(); 105 | } 106 | 107 | /** 108 | * Checks if a class is public 109 | */ 110 | private boolean isPublicClass(DeclaredType type) { 111 | Element element = type.asElement(); 112 | 113 | return element.getModifiers().contains(javax.lang.model.element.Modifier.PUBLIC); 114 | } 115 | 116 | /** 117 | * Checks if an public empty constructor is available 118 | */ 119 | private boolean hasPublicEmptyConstructor(DeclaredType type) { 120 | Element element = type.asElement(); 121 | 122 | List containing = element.getEnclosedElements(); 123 | 124 | for (Element e : containing) { 125 | if (e.getKind() == ElementKind.CONSTRUCTOR) { 126 | ExecutableElement c = (ExecutableElement) e; 127 | 128 | if ((c.getParameters() == null || c.getParameters().isEmpty()) && c.getModifiers() 129 | .contains(javax.lang.model.element.Modifier.PUBLIC)) { 130 | return true; 131 | } 132 | } 133 | } 134 | 135 | return false; 136 | } 137 | 138 | private String getFullQualifiedNameByTypeMirror(TypeMirror baggerClass) { 139 | if (baggerClass == null) { 140 | ProcessorMessage.error(element, "Could not get the bagger class"); 141 | return null; 142 | } 143 | 144 | if (baggerClass.getKind() != TypeKind.DECLARED) { 145 | ProcessorMessage.error(element, "@ %s is not a class in %s ", Bagger.class.getSimpleName(), 146 | element.getSimpleName()); 147 | return null; 148 | } 149 | 150 | if (!isPublicClass((DeclaredType) baggerClass)) { 151 | ProcessorMessage.error(element, "The %s must be a public class to be a valid Bagger", 152 | baggerClass.toString()); 153 | return null; 154 | } 155 | 156 | // Check if the bagger class has a default constructor 157 | if (!hasPublicEmptyConstructor((DeclaredType) baggerClass)) { 158 | ProcessorMessage.error(element, 159 | "The %s must provide a public empty default constructor to be a valid Bagger", 160 | baggerClass.toString()); 161 | return null; 162 | } 163 | 164 | return baggerClass.toString(); 165 | } 166 | 167 | public Element getElement() { 168 | return element; 169 | } 170 | 171 | public String getFieldName() { 172 | return fieldName; 173 | } 174 | 175 | public String getType() { 176 | return type; 177 | } 178 | 179 | public String getFullQualifiedBaggerName() { 180 | return baggerFullyQualifiedName; 181 | } 182 | 183 | public FieldCodeGen getCodeGenerator() { 184 | return codeGenerator; 185 | } 186 | 187 | public TypeMirror getGenericsTypeArgument() { 188 | return genericsTypeArgument; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/ParcelablePleaseProcessor.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor; 2 | 3 | import com.hannesdorfmann.parcelableplease.annotation.ParcelableNoThanks; 4 | import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease; 5 | import com.hannesdorfmann.parcelableplease.annotation.ParcelableThisPlease; 6 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Set; 10 | import javax.annotation.processing.AbstractProcessor; 11 | import javax.annotation.processing.Filer; 12 | import javax.annotation.processing.ProcessingEnvironment; 13 | import javax.annotation.processing.RoundEnvironment; 14 | import javax.annotation.processing.SupportedAnnotationTypes; 15 | import javax.lang.model.SourceVersion; 16 | import javax.lang.model.element.Element; 17 | import javax.lang.model.element.ElementKind; 18 | import javax.lang.model.element.Modifier; 19 | import javax.lang.model.element.TypeElement; 20 | import javax.lang.model.element.VariableElement; 21 | import javax.lang.model.util.Elements; 22 | import javax.lang.model.util.Types; 23 | 24 | /** 25 | * @author Hannes Dorfmann 26 | */ 27 | @SupportedAnnotationTypes("com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease") 28 | public class ParcelablePleaseProcessor extends AbstractProcessor { 29 | 30 | private static boolean BAGGERS_CREATED = false; 31 | 32 | private Elements elementUtils; 33 | private Types typeUtils; 34 | private Filer filer; 35 | 36 | @Override 37 | public synchronized void init(ProcessingEnvironment env) { 38 | super.init(env); 39 | 40 | elementUtils = env.getElementUtils(); 41 | typeUtils = env.getTypeUtils(); 42 | filer = env.getFiler(); 43 | ProcessorMessage.init(env); 44 | } 45 | 46 | @Override 47 | public boolean process(Set annotations, RoundEnvironment env) { 48 | 49 | Element lastElement = null; 50 | CodeGenerator codeGenerator = new CodeGenerator(elementUtils, filer); 51 | 52 | for (Element element : env.getElementsAnnotatedWith(ParcelablePlease.class)) { 53 | 54 | if (!isClass(element)) { 55 | continue; 56 | } 57 | 58 | List fields = new ArrayList(); 59 | 60 | lastElement = element; 61 | 62 | ParcelablePlease annotation = element.getAnnotation(ParcelablePlease.class); 63 | boolean allFields = annotation.allFields(); 64 | boolean ignorePrivateFields = annotation.ignorePrivateFields(); 65 | 66 | List memberFields = elementUtils.getAllMembers((TypeElement) element); 67 | 68 | if (memberFields != null) { 69 | for (Element member : memberFields) { 70 | // Search for fields 71 | 72 | if (member.getKind() != ElementKind.FIELD || !(member instanceof VariableElement)) { 73 | continue; // Not a field, so go on 74 | } 75 | 76 | // it's a field, so go on 77 | 78 | ParcelableNoThanks skipFieldAnnotation = member.getAnnotation(ParcelableNoThanks.class); 79 | if (skipFieldAnnotation != null) { 80 | // Field is marked as not parcelabel, so continue with the next 81 | continue; 82 | } 83 | 84 | if (!allFields) { 85 | ParcelableThisPlease fieldAnnotated = member.getAnnotation(ParcelableThisPlease.class); 86 | if (fieldAnnotated == null) { 87 | // Not all fields should parcelable, 88 | // and this field is not annotated as parcelable, so skip this field 89 | continue; 90 | } 91 | } 92 | 93 | // Check the visibility of the field and modifiers 94 | Set modifiers = member.getModifiers(); 95 | 96 | if (modifiers.contains(Modifier.STATIC)) { 97 | // Static fields are skipped 98 | continue; 99 | } 100 | 101 | if (modifiers.contains(Modifier.PRIVATE)) { 102 | 103 | if (ignorePrivateFields) { 104 | continue; 105 | } 106 | 107 | ProcessorMessage.error(member, 108 | "The field %s in %s is private. At least default package visibility is required " 109 | + "or annotate this field as not been parcelable with @%s " 110 | + "or configure this class to ignore private fields " 111 | + "with @%s( ignorePrivateFields = true )", member.getSimpleName(), 112 | element.getSimpleName(), ParcelableNoThanks.class.getSimpleName(), 113 | ParcelablePlease.class.getSimpleName()); 114 | } 115 | 116 | if (modifiers.contains(Modifier.FINAL)) { 117 | ProcessorMessage.error(member, 118 | "The field %s in %s is final. Final can not be Parcelable", element.getSimpleName(), 119 | member.getSimpleName()); 120 | } 121 | 122 | // If we are here the field is be parcelable 123 | fields.add(new ParcelableField((VariableElement) member, elementUtils, typeUtils)); 124 | } 125 | } 126 | 127 | // 128 | // Generate the code 129 | // 130 | 131 | try { 132 | codeGenerator.generate((TypeElement) element, fields); 133 | } catch (Exception e) { 134 | e.printStackTrace(); 135 | ProcessorMessage.error(lastElement, "An error has occurred while processing %s : %s", 136 | element.getSimpleName(), e.getMessage()); 137 | } 138 | 139 | } // End for loop 140 | 141 | return true; 142 | } 143 | 144 | @Override 145 | public SourceVersion getSupportedSourceVersion() { 146 | return SourceVersion.latestSupported(); 147 | } 148 | 149 | /** 150 | * Checks if the element is a class 151 | */ 152 | private boolean isClass(Element element) { 153 | if (element.getKind() == ElementKind.CLASS) { 154 | 155 | if (element.getModifiers().contains(Modifier.ABSTRACT)) { 156 | ProcessorMessage.error(element, 157 | "Element %s is annotated with @%s but is an abstract class. " 158 | + "Abstract classes can not be annotated. Annotate the concrete class " 159 | + "that implements all abstract methods with @%s", element.getSimpleName(), 160 | ParcelablePlease.class.getSimpleName(), ParcelablePlease.class.getSimpleName()); 161 | return false; 162 | } 163 | 164 | if (element.getModifiers().contains(Modifier.PRIVATE)) { 165 | ProcessorMessage.error(element, "The private class %s is annotated with @%s. " 166 | + "Private classes are not supported because of lacking visibility.", 167 | element.getSimpleName(), ParcelablePlease.class.getSimpleName()); 168 | return false; 169 | } 170 | 171 | // Ok, its a valid class 172 | return true; 173 | } else { 174 | ProcessorMessage.error(element, 175 | "Element %s is annotated with @%s but is not a class. Only Classes are supported", 176 | element.getSimpleName(), ParcelablePlease.class.getSimpleName()); 177 | return false; 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/ProcessorMessage.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor; 2 | 3 | import javax.annotation.processing.ProcessingEnvironment; 4 | import javax.lang.model.element.Element; 5 | import javax.tools.Diagnostic; 6 | 7 | /** 8 | * This is a simple static helper class to print error, waring or note messages during annotation 9 | * processing.

You must initialize this class by calling {@link 10 | * #init(ProcessingEnvironment)} before you can use the messaging methods

11 | * 12 | * @author Hannes Dorfmann 13 | */ 14 | public class ProcessorMessage { 15 | 16 | private static ProcessingEnvironment processingEnvironment; 17 | 18 | private ProcessorMessage(ProcessingEnvironment processingEnvironment) { 19 | this.processingEnvironment = processingEnvironment; 20 | } 21 | 22 | public static void init(ProcessingEnvironment processingEnv) { 23 | processingEnvironment = processingEnv; 24 | } 25 | 26 | public static void error(Element element, String message, Object... args) { 27 | if (args.length > 0) { 28 | message = String.format(message, args); 29 | } 30 | processingEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element); 31 | } 32 | 33 | public static void warn(Element element, String message, Object... args) { 34 | if (args.length > 0) { 35 | message = String.format(message, args); 36 | } 37 | processingEnvironment.getMessager().printMessage(Diagnostic.Kind.WARNING, message, element); 38 | } 39 | 40 | public static void note(Element element, String message, Object... args) { 41 | if (args.length > 0) { 42 | message = String.format(message, args); 43 | } 44 | processingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE, message, element); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/AbsCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import java.io.IOException; 5 | import repacked.com.squareup.javawriter.JavaWriter; 6 | 7 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_PARCEL; 8 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_SOURCE; 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_TARGET; 10 | 11 | /** 12 | * @author Hannes Dorfmann 13 | */ 14 | public class AbsCodeGen implements FieldCodeGen { 15 | 16 | protected String methodSuffix; 17 | 18 | public AbsCodeGen(String methodSuffix) { 19 | this.methodSuffix = methodSuffix; 20 | } 21 | 22 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter javaWriter) 23 | throws IOException { 24 | 25 | javaWriter.emitStatement("%s.write%s(%s.%s)", PARAM_PARCEL, methodSuffix, PARAM_SOURCE, 26 | field.getFieldName()); 27 | } 28 | 29 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter javaWriter) 30 | throws IOException { 31 | 32 | javaWriter.emitStatement("%s.%s = %s.read%s()", PARAM_TARGET, field.getFieldName(), PARAM_PARCEL, methodSuffix); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/BaggerCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import java.io.IOException; 5 | import repacked.com.squareup.javawriter.JavaWriter; 6 | 7 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_FLAGS; 8 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_PARCEL; 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_SOURCE; 10 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_TARGET; 11 | 12 | /** 13 | * A code generator for Baggers. 14 | * 15 | * @author Hannes Dorfmann 16 | */ 17 | public class BaggerCodeGen implements FieldCodeGen { 18 | 19 | public BaggerCodeGen() { 20 | 21 | } 22 | 23 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter jw) 24 | throws IOException { 25 | 26 | jw.emitStatement("new %s().write(%s.%s, %s, %s)", field.getFullQualifiedBaggerName(), 27 | PARAM_SOURCE, field.getFieldName(), PARAM_PARCEL, PARAM_FLAGS); 28 | } 29 | 30 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter jw) 31 | throws IOException { 32 | 33 | jw.emitStatement("%s.%s = new %s().read(%s)", PARAM_TARGET, field.getFieldName(), 34 | field.getFullQualifiedBaggerName(), PARAM_PARCEL); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/CodeGenerator.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator; 2 | 3 | import com.hannesdorfmann.parcelableplease.annotation.Bagger; 4 | import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease; 5 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 6 | import com.hannesdorfmann.parcelableplease.processor.ProcessorMessage; 7 | import com.hannesdorfmann.parcelableplease.processor.util.TypeUtils; 8 | import java.io.IOException; 9 | import java.io.Writer; 10 | import java.util.EnumSet; 11 | import java.util.List; 12 | import javax.annotation.processing.Filer; 13 | import javax.lang.model.element.Modifier; 14 | import javax.lang.model.element.TypeElement; 15 | import javax.lang.model.util.Elements; 16 | import javax.tools.JavaFileObject; 17 | import repacked.com.squareup.javawriter.JavaWriter; 18 | 19 | /** 20 | * Generates the javacode for each field 21 | * 22 | * @author Hannes Dorfmann 23 | */ 24 | public class CodeGenerator { 25 | 26 | public static final String PARAM_TARGET = "target"; 27 | 28 | public static final String PARAM_SOURCE = "source"; 29 | 30 | public static final String PARAM_FLAGS = "flags"; 31 | 32 | /** 33 | * The parameter name for the 34 | */ 35 | public static final String PARAM_PARCEL = "parcel"; 36 | 37 | private Filer filer; 38 | private Elements elementUtils; 39 | 40 | public CodeGenerator(Elements elementUtils, Filer filer) { 41 | this.filer = filer; 42 | this.elementUtils = elementUtils; 43 | } 44 | 45 | public void generate(TypeElement classElement, List fields) throws Exception { 46 | 47 | String classSuffix = "ParcelablePlease"; 48 | String packageName = TypeUtils.getPackageName(elementUtils, classElement); 49 | String binaryName = TypeUtils.getBinaryName(elementUtils, classElement); 50 | String originFullQualifiedName = classElement.getQualifiedName().toString(); 51 | String className; 52 | if (packageName.length() > 0) { 53 | className = binaryName.substring(packageName.length() + 1) + classSuffix; 54 | } else { 55 | className = binaryName + classSuffix; 56 | } 57 | String qualifiedName = binaryName + classSuffix; 58 | 59 | // 60 | // Write code 61 | // 62 | 63 | JavaFileObject jfo = filer.createSourceFile(qualifiedName, classElement); 64 | Writer writer = jfo.openWriter(); 65 | JavaWriter jw = new JavaWriter(writer); 66 | 67 | jw.emitPackage(packageName); 68 | jw.emitImports("android.os.Parcel"); 69 | jw.emitEmptyLine(); 70 | jw.emitJavadoc("Generated class by @%s . Do not modify this code!", 71 | ParcelablePlease.class.getSimpleName()); 72 | jw.beginType(className, "class", EnumSet.of(Modifier.PUBLIC)); 73 | jw.emitEmptyLine(); 74 | 75 | generateWriteToParcel(jw, originFullQualifiedName, fields); 76 | jw.emitEmptyLine(); 77 | generateReadFromParcel(jw, originFullQualifiedName, fields); 78 | 79 | jw.endType(); 80 | jw.close(); 81 | } 82 | 83 | /** 84 | * Generate the writeToParcel method 85 | * 86 | * @throws IOException 87 | */ 88 | private void generateWriteToParcel(JavaWriter jw, String originClass, 89 | List fields) throws IOException { 90 | 91 | jw.beginMethod("void", "writeToParcel", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), 92 | originClass, PARAM_SOURCE, "Parcel", PARAM_PARCEL, "int", PARAM_FLAGS); 93 | 94 | for (ParcelableField field : fields) { 95 | FieldCodeGen gen = field.getCodeGenerator(); 96 | 97 | if (gen == null) { // Already checked before, but let's check it again 98 | ProcessorMessage.error(field.getElement(), 99 | "The field %s is not Parcelable or of unsupported type. Use a @%s", 100 | field.getFieldName(), 101 | Bagger.class.getSimpleName() + " to provide your own serialisation mechanism"); 102 | 103 | throw new IOException("Unparcelable Field " + field.getFieldName()); 104 | } 105 | 106 | jw.emitEmptyLine(); 107 | gen.generateWriteToParcel(field, jw); 108 | } 109 | 110 | jw.endMethod(); 111 | } 112 | 113 | private void generateReadFromParcel(JavaWriter jw, String originClass, 114 | List fields) throws IOException { 115 | 116 | jw.beginMethod("void", "readFromParcel", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), 117 | originClass, PARAM_TARGET, "Parcel", PARAM_PARCEL); 118 | 119 | for (ParcelableField field : fields) { 120 | FieldCodeGen gen = field.getCodeGenerator(); 121 | 122 | if (gen == null) { // Already checked before, but let's check it again 123 | ProcessorMessage.error(field.getElement(), 124 | "The field %s is not Parcelable or of unsupported type. Use a @%s", 125 | field.getFieldName(), 126 | Bagger.class.getSimpleName() + " to provide your own serialisation mechanism"); 127 | 128 | throw new IOException("Unparcelable Field " + field.getFieldName()); 129 | } 130 | 131 | jw.emitEmptyLine(); 132 | gen.generateReadFromParcel(field, jw); 133 | } 134 | 135 | jw.endMethod(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/FieldCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import java.io.IOException; 5 | import repacked.com.squareup.javawriter.JavaWriter; 6 | 7 | /** 8 | * @author Hannes Dorfmann 9 | */ 10 | public interface FieldCodeGen { 11 | 12 | public void generateWriteToParcel(ParcelableField field, JavaWriter javaWriter) 13 | throws IOException; 14 | 15 | public void generateReadFromParcel(ParcelableField field, JavaWriter javaWriter) 16 | throws IOException; 17 | } 18 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/SupportedTypes.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator; 2 | 3 | import com.hannesdorfmann.parcelableplease.annotation.Bagger; 4 | import com.hannesdorfmann.parcelableplease.processor.ProcessorMessage; 5 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.android.BundleCodeGen; 6 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.android.ParcelableCodeGen; 7 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.android.SparseBooleanCodeGen; 8 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.collection.AbsListCodeGen; 9 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.collection.ParcelableArrayCodeGen; 10 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.collection.PrimitiveArrayCodeGen; 11 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.collection.StringListCodeGen; 12 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.other.DateCodeGen; 13 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.other.SerializeableCodeGen; 14 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.primitives.BooleanCodeGen; 15 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.primitiveswrapper.AbsPrimitiveWrapperCodeGen; 16 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.primitiveswrapper.BooleanWrapperCodeGen; 17 | import com.hannesdorfmann.parcelableplease.processor.util.CodeGenInfo; 18 | import java.util.ArrayList; 19 | import java.util.Date; 20 | import java.util.HashMap; 21 | import java.util.LinkedList; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.concurrent.CopyOnWriteArrayList; 25 | import javax.lang.model.element.Element; 26 | import javax.lang.model.element.TypeElement; 27 | import javax.lang.model.element.VariableElement; 28 | import javax.lang.model.type.ArrayType; 29 | import javax.lang.model.type.DeclaredType; 30 | import javax.lang.model.type.TypeKind; 31 | import javax.lang.model.type.TypeMirror; 32 | import javax.lang.model.util.Elements; 33 | import javax.lang.model.util.Types; 34 | 35 | /** 36 | * The supported types and the corresponding code generator classes 37 | * 38 | * @author Hannes Dorfmann 39 | */ 40 | public class SupportedTypes { 41 | 42 | private static final String TYPE_KEY_PARCELABLE = "android.os.Parcelable"; 43 | private static final String TYPE_KEY_SERIALIZABLE = "java.io.Serializable"; 44 | private static final String TYPE_KEY_PARCELABLE_ARRAYLIST = "Parcelable-ArrayList"; 45 | private static final String TYPE_KEY_PARCELABLE_LINKEDLIST = "Parcelable-LinkedList"; 46 | private static final String TYPE_KEY_PARCELABLE_LIST = "Parcelable-List"; 47 | private static final String TYPE_KEY_STRING_LIST = "String-List"; 48 | private static final String TYPE_KEY_BUNDLE = "android.os.Bundle"; 49 | private static final String TYPE_KEY_DOUBLE_ARRAY = "Array-Double"; 50 | private static final String TYPE_KEY_FLOAT_ARRAY = "Array-Float"; 51 | private static final String TYPE_KEY_INT_ARRAY = "Array-Int"; 52 | private static final String TYPE_KEY_BOOL_ARRAY = "Array-Boolean"; 53 | private static final String TYPE_KEY_BYTE_ARRAY = "Array-Byte"; 54 | private static final String TYPE_KEY_CHAR_ARRAY = "Array-Char"; 55 | private static final String TYPE_KEY_LONG_ARRAY = "Array-Long"; 56 | private static final String TYPE_KEY_STRING_ARRAY = "Array-String"; 57 | private static final String TYPE_KEY_PARCELABLE_ARRAY = "Array-Parcelable"; 58 | private static final String TYPE_KEY_SPARSE_BOOLEAN_ARRAY = "android.util.SparseBooleanArray"; 59 | private static final String TYPE_KEY_SPARSE_ARRAY = "android.util.SparseArray"; 60 | 61 | private static final String TYPE_KEY_PARCELABLE_COPYONWRITEARRAYLIST = 62 | "Parcelable-CopyOnWriteArrayList"; 63 | 64 | private static Map typeMap; 65 | 66 | static { 67 | typeMap = new HashMap(); 68 | 69 | // primitives 70 | typeMap.put(byte.class.getCanonicalName(), new AbsCodeGen("Byte")); 71 | typeMap.put(boolean.class.getCanonicalName(), new BooleanCodeGen()); 72 | typeMap.put(double.class.getCanonicalName(), new AbsCodeGen("Double")); 73 | typeMap.put(float.class.getCanonicalName(), new AbsCodeGen("Float")); 74 | typeMap.put(int.class.getCanonicalName(), new AbsCodeGen("Int")); 75 | typeMap.put(long.class.getCanonicalName(), new AbsCodeGen("Long")); 76 | typeMap.put(String.class.getCanonicalName(), new AbsCodeGen("String")); 77 | 78 | // Wrapper classes 79 | typeMap.put(Byte.class.getCanonicalName(), new AbsPrimitiveWrapperCodeGen("Byte")); 80 | typeMap.put(Boolean.class.getCanonicalName(), new BooleanWrapperCodeGen()); 81 | typeMap.put(Double.class.getCanonicalName(), new AbsPrimitiveWrapperCodeGen("Double")); 82 | typeMap.put(Float.class.getCanonicalName(), new AbsPrimitiveWrapperCodeGen("Float")); 83 | typeMap.put(Integer.class.getCanonicalName(), new AbsPrimitiveWrapperCodeGen("Int")); 84 | typeMap.put(Long.class.getCanonicalName(), new AbsPrimitiveWrapperCodeGen("Long")); 85 | 86 | // Android 87 | typeMap.put(TYPE_KEY_PARCELABLE, new ParcelableCodeGen()); 88 | typeMap.put(TYPE_KEY_BUNDLE, new BundleCodeGen()); 89 | typeMap.put(TYPE_KEY_SPARSE_BOOLEAN_ARRAY, new SparseBooleanCodeGen()); 90 | // typeMap.put(TYPE_KEY_SPARSE_ARRAY, new SparseArrayCodeGen()); // TODO implement 91 | 92 | // Lists 93 | typeMap.put(TYPE_KEY_STRING_LIST, new StringListCodeGen()); 94 | typeMap.put(TYPE_KEY_PARCELABLE_LIST, new AbsListCodeGen(ArrayList.class.getName())); 95 | typeMap.put(TYPE_KEY_PARCELABLE_ARRAYLIST, new AbsListCodeGen(ArrayList.class.getName())); 96 | typeMap.put(TYPE_KEY_PARCELABLE_LINKEDLIST, new AbsListCodeGen(LinkedList.class.getName())); 97 | typeMap.put(TYPE_KEY_PARCELABLE_COPYONWRITEARRAYLIST, 98 | new AbsListCodeGen(CopyOnWriteArrayList.class.getName())); 99 | 100 | // Arrays 101 | typeMap.put(TYPE_KEY_BOOL_ARRAY, new PrimitiveArrayCodeGen("BooleanArray", "boolean")); 102 | typeMap.put(TYPE_KEY_BYTE_ARRAY, new PrimitiveArrayCodeGen("ByteArray", "byte")); 103 | typeMap.put(TYPE_KEY_CHAR_ARRAY, new PrimitiveArrayCodeGen("CharArray", "char")); 104 | typeMap.put(TYPE_KEY_DOUBLE_ARRAY, new PrimitiveArrayCodeGen("DoubleArray", "double")); 105 | typeMap.put(TYPE_KEY_FLOAT_ARRAY, new PrimitiveArrayCodeGen("FloatArray", "float")); 106 | typeMap.put(TYPE_KEY_LONG_ARRAY, new PrimitiveArrayCodeGen("LongArray", "long")); 107 | typeMap.put(TYPE_KEY_INT_ARRAY, new PrimitiveArrayCodeGen("IntArray", "int")); 108 | typeMap.put(TYPE_KEY_STRING_ARRAY, new PrimitiveArrayCodeGen("StringArray", "String")); 109 | typeMap.put(TYPE_KEY_PARCELABLE_ARRAY, new ParcelableArrayCodeGen()); 110 | 111 | // Other common classes 112 | typeMap.put(TYPE_KEY_SERIALIZABLE, new SerializeableCodeGen()); 113 | typeMap.put(Date.class.getCanonicalName(), new DateCodeGen()); 114 | } 115 | 116 | /** 117 | * Collect information about the element and the corresponding code generator 118 | */ 119 | public static CodeGenInfo getCodeGenInfo(VariableElement element, Elements elements, 120 | Types types) { 121 | 122 | // Special classes, primitive, wrappers, etc. 123 | String typeKey = element.asType().toString(); 124 | if (typeMap.get(typeKey) != null) { 125 | return new CodeGenInfo(typeMap.get(typeKey)); 126 | } 127 | 128 | // Check if its a simple parcelable 129 | if (isOfType(element, "android.os.Parcelable", elements, types)) { 130 | return new CodeGenInfo(typeMap.get(TYPE_KEY_PARCELABLE)); 131 | } 132 | 133 | // Lists 134 | if (isOfWildCardType(element, ArrayList.class.getName(), "android.os.Parcelable", elements, 135 | types)) { 136 | 137 | return new CodeGenInfo(typeMap.get(TYPE_KEY_PARCELABLE_ARRAYLIST), 138 | hasGenericsTypeArgumentOf(element, "android.os.Parcelable", elements, types)); 139 | } 140 | 141 | if (isOfWildCardType(element, LinkedList.class.getName(), "android.os.Parcelable", elements, 142 | types)) { 143 | return new CodeGenInfo(typeMap.get(TYPE_KEY_PARCELABLE_LINKEDLIST), 144 | hasGenericsTypeArgumentOf(element, "android.os.Parcelable", elements, types)); 145 | } 146 | 147 | if (isOfWildCardType(element, CopyOnWriteArrayList.class.getName(), "android.os.Parcelable", 148 | elements, types)) { 149 | return new CodeGenInfo(typeMap.get(TYPE_KEY_PARCELABLE_COPYONWRITEARRAYLIST), 150 | hasGenericsTypeArgumentOf(element, "android.os.Parcelable", elements, types)); 151 | } 152 | 153 | if (isOfWildCardType(element, List.class.getName(), "android.os.Parcelable", elements, types)) { 154 | return new CodeGenInfo(typeMap.get(TYPE_KEY_PARCELABLE_LIST), 155 | hasGenericsTypeArgumentOf(element, "android.os.Parcelable", elements, types)); 156 | } 157 | 158 | if (isOfWildCardType(element, List.class.getName(), String.class.getName(), elements, types)) { 159 | return new CodeGenInfo(typeMap.get(TYPE_KEY_STRING_LIST)); 160 | } 161 | 162 | // Arrays 163 | if (element.asType().getKind() == TypeKind.ARRAY) { 164 | 165 | ArrayType arrayType = (ArrayType) element.asType(); 166 | 167 | TypeMirror arrayOf = arrayType.getComponentType(); 168 | 169 | if (arrayOf.getKind() == TypeKind.CHAR) { 170 | return new CodeGenInfo(typeMap.get(TYPE_KEY_CHAR_ARRAY)); 171 | } 172 | 173 | if (arrayOf.getKind() == TypeKind.BOOLEAN) { 174 | return new CodeGenInfo(typeMap.get(TYPE_KEY_BOOL_ARRAY)); 175 | } 176 | 177 | if (arrayOf.getKind() == TypeKind.BYTE) { 178 | return new CodeGenInfo(typeMap.get(TYPE_KEY_BYTE_ARRAY)); 179 | } 180 | 181 | if (arrayOf.getKind() == TypeKind.DOUBLE) { 182 | return new CodeGenInfo(typeMap.get(TYPE_KEY_DOUBLE_ARRAY)); 183 | } 184 | 185 | if (arrayOf.getKind() == TypeKind.FLOAT) { 186 | return new CodeGenInfo(typeMap.get(TYPE_KEY_FLOAT_ARRAY)); 187 | } 188 | 189 | if (arrayOf.getKind() == TypeKind.INT) { 190 | return new CodeGenInfo(typeMap.get(TYPE_KEY_INT_ARRAY)); 191 | } 192 | 193 | if (arrayOf.getKind() == TypeKind.LONG) { 194 | return new CodeGenInfo(typeMap.get(TYPE_KEY_LONG_ARRAY)); 195 | } 196 | 197 | if (arrayOf.toString().equals(String.class.getName())) { 198 | return new CodeGenInfo(typeMap.get(TYPE_KEY_STRING_ARRAY)); 199 | } 200 | 201 | if (isOfType(arrayOf, "android.os.Parcelable", elements, types)) { 202 | // It's an array of parcelable 203 | return new CodeGenInfo(typeMap.get(TYPE_KEY_PARCELABLE_ARRAY), arrayOf); 204 | } 205 | 206 | // Unsupported Array Type 207 | ProcessorMessage.error(element, "Unsuppored type %s as Array type for field %s. " 208 | + "You could write your own Serialization mechanism by using @%s ", 209 | element.asType().toString(), element.getSimpleName(), Bagger.class.getSimpleName()); 210 | } 211 | 212 | 213 | // TODO SparseArray 214 | // if (isOfWildCardType(element, "android.util.SparseArray", "android.os.Parcelable", elements, 215 | // types)) { 216 | // return new CodeGenInfo(typeMap.get(TYPE_KEY_SPARSE_ARRAY), 217 | // hasGenericsTypeArgumentOf(element, null, elements, types)); 218 | // } 219 | 220 | 221 | // Serializable as last 222 | if (isOfType(element, "java.io.Serializable", elements, types)) { 223 | return new CodeGenInfo(typeMap.get(TYPE_KEY_SERIALIZABLE)); 224 | } 225 | 226 | // Unsupported type 227 | ProcessorMessage.error(element, "Unsuppored type %s for field %s. " 228 | + "You could write your own Serialization mechanism by using @%s ", 229 | element.asType().toString(), element.getSimpleName(), Bagger.class.getSimpleName()); 230 | 231 | return new CodeGenInfo(null); 232 | } 233 | 234 | /** 235 | * Get the wildcardType 236 | */ 237 | public static TypeMirror getWildcardType(String type, String elementType, Elements elements, 238 | Types types) { 239 | TypeElement arrayList = elements.getTypeElement(type); 240 | TypeMirror elType = elements.getTypeElement(elementType).asType(); 241 | return types.getDeclaredType(arrayList, types.getWildcardType(elType, null)); 242 | } 243 | 244 | private static boolean isOfWildCardType(Element element, String type, String wildcardtype, 245 | Elements elements, Types types) { 246 | return types.isAssignable(element.asType(), 247 | getWildcardType(type, wildcardtype, elements, types)); 248 | } 249 | 250 | private static boolean isOfType(Element element, String type, Elements elements, Types types) { 251 | return isOfType(element.asType(), type, elements, types); 252 | } 253 | 254 | private static boolean isOfType(TypeMirror typeMirror, String type, Elements elements, 255 | Types types) { 256 | return types.isAssignable(typeMirror, elements.getTypeElement(type).asType()); 257 | } 258 | 259 | /** 260 | * Checks if the variabel element has generics arguments that matches the expected type 261 | */ 262 | public static TypeMirror hasGenericsTypeArgumentOf(Element element, String typeToCheck, 263 | Elements elements, Types types) { 264 | 265 | if (element.asType().getKind() != TypeKind.DECLARED 266 | || !(element.asType() instanceof DeclaredType)) { 267 | ProcessorMessage.error(element, "The field %s in %s doesn't have generic type arguments!", 268 | element.getSimpleName(), element.asType().toString()); 269 | } 270 | 271 | DeclaredType declaredType = (DeclaredType) element.asType(); 272 | List typeArguments = declaredType.getTypeArguments(); 273 | 274 | if (typeArguments.isEmpty()) { 275 | ProcessorMessage.error(element, "The field %s in %s doesn't have generic type arguments!", 276 | element.getSimpleName(), element.asType().toString()); 277 | } 278 | 279 | if (typeArguments.size() > 1) { 280 | ProcessorMessage.error(element, "The field %s in %s has more than 1 generic type argument!", 281 | element.getSimpleName(), element.asType().toString()); 282 | } 283 | 284 | // Ok it has a generic argument, check if this extends Parcelable 285 | TypeMirror argument = typeArguments.get(0); 286 | 287 | if (typeToCheck != null) { 288 | if (!isOfType(argument, typeToCheck, elements, types)) { 289 | ProcessorMessage.error(element, 290 | "The fields %s generic type argument is not of type %s! (in %s )", 291 | element.getSimpleName(), typeToCheck, element.asType().toString()); 292 | } 293 | } 294 | 295 | // everything is like expected 296 | return argument; 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/android/BundleCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.android; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.AbsCodeGen; 4 | 5 | /** 6 | * Bundle code generator 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | public class BundleCodeGen extends AbsCodeGen { 11 | 12 | public BundleCodeGen() { 13 | super("Bundle"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/android/ParcelableCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.android; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 5 | import java.io.IOException; 6 | import repacked.com.squareup.javawriter.JavaWriter; 7 | 8 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_FLAGS; 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_PARCEL; 10 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_SOURCE; 11 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_TARGET; 12 | 13 | /** 14 | * @author Hannes Dorfmann 15 | */ 16 | public class ParcelableCodeGen implements FieldCodeGen { 17 | 18 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter javaWriter) 19 | throws IOException { 20 | 21 | javaWriter.emitStatement("%s.writeParcelable(%s.%s, %s)", PARAM_PARCEL, PARAM_SOURCE, 22 | field.getFieldName(), PARAM_FLAGS); 23 | } 24 | 25 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter javaWriter) 26 | throws IOException { 27 | 28 | javaWriter.emitStatement("%s.%s = %s.readParcelable(%s.class.getClassLoader())", PARAM_TARGET, field.getFieldName(), 29 | PARAM_PARCEL, field.getType()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/android/SparseArrayCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.android; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 5 | import java.io.IOException; 6 | import repacked.com.squareup.javawriter.JavaWriter; 7 | 8 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_PARCEL; 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_SOURCE; 10 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_TARGET; 11 | 12 | /** 13 | * @author Hannes Dorfmann 14 | */ 15 | public class SparseArrayCodeGen implements FieldCodeGen { 16 | 17 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter javaWriter) 18 | throws IOException { 19 | 20 | javaWriter.emitStatement("%s.writeSparseArray(%s.%s)", PARAM_PARCEL, PARAM_SOURCE, 21 | field.getFieldName()); 22 | } 23 | 24 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter javaWriter) 25 | throws IOException { 26 | 27 | javaWriter.emitStatement("%s.%s = %s.readSparseArray(%s.class.getClassLoader())", PARAM_TARGET, 28 | field.getFieldName(), PARAM_PARCEL, field.getGenericsTypeArgument().toString()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/android/SparseBooleanCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.android; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.AbsCodeGen; 4 | 5 | /** 6 | * SparseBooleanArray 7 | * 8 | * @author Hannes Dorfmann 9 | */ 10 | public class SparseBooleanCodeGen extends AbsCodeGen { 11 | 12 | public SparseBooleanCodeGen() { 13 | super("SparseBooleanArray"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/collection/AbsListCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.collection; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator; 5 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 6 | import java.io.IOException; 7 | import repacked.com.squareup.javawriter.JavaWriter; 8 | 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_PARCEL; 10 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_SOURCE; 11 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_TARGET; 12 | 13 | /** 14 | * It also handles null values 15 | * 16 | * @author Hannes Dorfmann 17 | */ 18 | public class AbsListCodeGen implements FieldCodeGen { 19 | 20 | private String listType; 21 | 22 | public AbsListCodeGen(String listType) { 23 | this.listType = listType; 24 | } 25 | 26 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter jw) 27 | throws IOException { 28 | 29 | jw.emitStatement("%s.writeByte( (byte) (%s.%s != null ? 1 : 0) )", CodeGenerator.PARAM_PARCEL, 30 | CodeGenerator.PARAM_SOURCE, field.getFieldName()); 31 | 32 | jw.beginControlFlow("if (%s.%s != null)", CodeGenerator.PARAM_SOURCE, field.getFieldName()); 33 | jw.emitStatement("%s.writeList(%s.%s)", PARAM_PARCEL, PARAM_SOURCE, field.getFieldName()); 34 | jw.endControlFlow(); 35 | } 36 | 37 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter jw) 38 | throws IOException { 39 | 40 | String typeArguments = field.getGenericsTypeArgument().toString(); 41 | 42 | jw.emitStatement("boolean %sNullHelper", field.getFieldName()); 43 | jw.emitStatement("%sNullHelper = ( %s.readByte() == 1 )", field.getFieldName(), 44 | CodeGenerator.PARAM_PARCEL); 45 | 46 | jw.beginControlFlow("if (%sNullHelper)", field.getFieldName()); 47 | jw.emitStatement("%s<%s> %sListHelper = new %s<%s>()", listType, typeArguments, 48 | field.getFieldName(), listType, typeArguments); 49 | jw.emitStatement("%s.readList(%sListHelper, %s.class.getClassLoader())", PARAM_PARCEL, 50 | field.getFieldName(), typeArguments); 51 | jw.emitStatement("%s.%s = %sListHelper", PARAM_TARGET, field.getFieldName(), 52 | field.getFieldName()); 53 | jw.nextControlFlow("else"); 54 | jw.emitStatement("%s.%s = null", PARAM_TARGET, field.getFieldName()); 55 | jw.endControlFlow(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/collection/ParcelableArrayCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.collection; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 5 | import java.io.IOException; 6 | import repacked.com.squareup.javawriter.JavaWriter; 7 | 8 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_FLAGS; 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_PARCEL; 10 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_SOURCE; 11 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_TARGET; 12 | 13 | /** 14 | * Parcelable array 15 | * 16 | * @author Hannes Dorfmann 17 | */ 18 | public class ParcelableArrayCodeGen implements FieldCodeGen { 19 | 20 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter jw) 21 | throws IOException { 22 | 23 | jw.emitStatement("%s.writeParcelableArray(%s.%s, %s)", PARAM_PARCEL, PARAM_SOURCE, 24 | field.getFieldName(), PARAM_FLAGS); 25 | } 26 | 27 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter jw) 28 | throws IOException { 29 | 30 | String type = field.getGenericsTypeArgument().toString(); 31 | 32 | jw.emitStatement( 33 | "android.os.Parcelable[] %sNullHelper = %s.readParcelableArray(%s.class.getClassLoader())", 34 | field.getFieldName(), PARAM_PARCEL, type); 35 | jw.beginControlFlow("if (%sNullHelper != null)", field.getFieldName()); 36 | jw.emitStatement( 37 | "%s.%s = java.util.Arrays.copyOf(%sNullHelper, %sNullHelper.length, %s[].class)", 38 | PARAM_TARGET, field.getFieldName(), field.getFieldName(), field.getFieldName(), type); 39 | jw.nextControlFlow("else"); 40 | jw.emitStatement("%s.%s = null", PARAM_TARGET, field.getFieldName()); 41 | jw.endControlFlow(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/collection/PrimitiveArrayCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.collection; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator; 5 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 6 | import java.io.IOException; 7 | import repacked.com.squareup.javawriter.JavaWriter; 8 | 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_PARCEL; 10 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_SOURCE; 11 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_TARGET; 12 | 13 | /** 14 | * It also handles null values 15 | * 16 | * @author Hannes Dorfmann 17 | */ 18 | public class PrimitiveArrayCodeGen implements FieldCodeGen { 19 | 20 | private String methodSuffix; 21 | private String type; 22 | 23 | public PrimitiveArrayCodeGen(String methodSuffix, String type) { 24 | this.methodSuffix = methodSuffix; 25 | this.type = type; 26 | } 27 | 28 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter jw) 29 | throws IOException { 30 | 31 | // -1 means that it array is null. Otherwise its array.length 32 | 33 | jw.emitStatement("%s.writeInt( (%s.%s != null ? %s.%s.length : -1) )", 34 | CodeGenerator.PARAM_PARCEL, CodeGenerator.PARAM_SOURCE, field.getFieldName(), 35 | CodeGenerator.PARAM_SOURCE, field.getFieldName()); 36 | 37 | jw.beginControlFlow("if (%s.%s != null)", CodeGenerator.PARAM_SOURCE, field.getFieldName()); 38 | jw.emitStatement("%s.write%s(%s.%s)", PARAM_PARCEL, methodSuffix, PARAM_SOURCE, 39 | field.getFieldName()); 40 | jw.endControlFlow(); 41 | } 42 | 43 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter jw) 44 | throws IOException { 45 | 46 | jw.emitStatement("int %sLengthHelper = -1", field.getFieldName()); 47 | jw.emitStatement("%sLengthHelper = %s.readInt()", field.getFieldName(), 48 | CodeGenerator.PARAM_PARCEL); 49 | 50 | jw.beginControlFlow("if (%sLengthHelper >= 0)", field.getFieldName()); 51 | jw.emitStatement("%s[] %sArrayHelper = new %s[%sLengthHelper]", type, field.getFieldName(), 52 | type, field.getFieldName()); 53 | jw.emitStatement("%s.read%s(%sArrayHelper)", PARAM_PARCEL, methodSuffix, field.getFieldName()); 54 | jw.emitStatement("%s.%s = %sArrayHelper", PARAM_TARGET, field.getFieldName(), 55 | field.getFieldName()); 56 | jw.nextControlFlow("else"); 57 | jw.emitStatement("%s.%s = null", PARAM_TARGET, field.getFieldName()); 58 | jw.endControlFlow(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/collection/StringListCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.collection; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 5 | import repacked.com.squareup.javawriter.JavaWriter; 6 | 7 | import java.io.IOException; 8 | 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.*; 10 | 11 | /** 12 | * String list 13 | * 14 | * @author Yaroslav Heriatovych 15 | */ 16 | public class StringListCodeGen implements FieldCodeGen { 17 | 18 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter jw) 19 | throws IOException { 20 | 21 | jw.emitStatement("%s.writeStringList(%s.%s)", PARAM_PARCEL, PARAM_SOURCE, 22 | field.getFieldName()); 23 | } 24 | 25 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter jw) 26 | throws IOException { 27 | 28 | jw.emitStatement("%s.%s = %s.createStringArrayList()", PARAM_TARGET, field.getFieldName(), PARAM_PARCEL); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/other/DateCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.other; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator; 5 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 6 | import java.io.IOException; 7 | import repacked.com.squareup.javawriter.JavaWriter; 8 | 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_PARCEL; 10 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_SOURCE; 11 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_TARGET; 12 | 13 | /** 14 | * It also handles null values 15 | * 16 | * @author Hannes Dorfmann 17 | */ 18 | public class DateCodeGen implements FieldCodeGen { 19 | 20 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter jw) 21 | throws IOException { 22 | 23 | jw.emitStatement("%s.writeByte( (byte) (%s.%s != null ? 1 : 0) )", CodeGenerator.PARAM_PARCEL, 24 | CodeGenerator.PARAM_SOURCE, field.getFieldName()); 25 | 26 | jw.beginControlFlow("if (%s.%s != null)", CodeGenerator.PARAM_SOURCE, field.getFieldName()); 27 | jw.emitStatement("%s.writeLong(%s.%s.getTime())", PARAM_PARCEL, PARAM_SOURCE, 28 | field.getFieldName()); 29 | jw.endControlFlow(); 30 | } 31 | 32 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter jw) 33 | throws IOException { 34 | 35 | jw.emitStatement("boolean %sNullHelper", field.getFieldName()); 36 | jw.emitStatement("%sNullHelper = ( %s.readByte() == 1 )", field.getFieldName(), 37 | CodeGenerator.PARAM_PARCEL); 38 | 39 | jw.beginControlFlow("if (%sNullHelper)", field.getFieldName()); 40 | jw.emitStatement("long %sLongHelper = %s.readLong()", field.getFieldName(), PARAM_PARCEL); 41 | jw.emitStatement("%s.%s = new java.util.Date(%sLongHelper)", PARAM_TARGET, field.getFieldName(), 42 | field.getFieldName()); 43 | jw.nextControlFlow("else"); 44 | jw.emitStatement("%s.%s = null", PARAM_TARGET, field.getFieldName()); 45 | jw.endControlFlow(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/other/SerializeableCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.other; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.AbsCodeGen; 5 | import java.io.IOException; 6 | import repacked.com.squareup.javawriter.JavaWriter; 7 | 8 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_PARCEL; 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_TARGET; 10 | 11 | /** 12 | * Code generartor for Serializable 13 | * 14 | * @author Hannes Dorfmann 15 | */ 16 | public class SerializeableCodeGen extends AbsCodeGen { 17 | 18 | public SerializeableCodeGen() { 19 | super("Serializable"); 20 | } 21 | 22 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter javaWriter) 23 | throws IOException { 24 | 25 | javaWriter.emitStatement("%s.%s = (%s) %s.readSerializable()", PARAM_TARGET, 26 | field.getFieldName(), field.getType(), PARAM_PARCEL); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/primitives/BooleanCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.primitives; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator; 5 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 6 | import java.io.IOException; 7 | import repacked.com.squareup.javawriter.JavaWriter; 8 | 9 | /** 10 | * For boolean primitives 11 | * 12 | * @author Hannes Dorfmann 13 | */ 14 | public class BooleanCodeGen implements FieldCodeGen { 15 | 16 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter javaWriter) 17 | throws IOException { 18 | 19 | javaWriter.emitStatement("%s.writeByte( (byte) (%s.%s? 1 : 0))", CodeGenerator.PARAM_PARCEL, 20 | CodeGenerator.PARAM_SOURCE, field.getFieldName()); 21 | } 22 | 23 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter javaWriter) 24 | throws IOException { 25 | 26 | javaWriter.emitStatement("%s.%s = ( %s.readByte() == 1 )", CodeGenerator.PARAM_TARGET, 27 | field.getFieldName(), CodeGenerator.PARAM_PARCEL); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/primitiveswrapper/AbsPrimitiveWrapperCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.primitiveswrapper; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator; 5 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 6 | import java.io.IOException; 7 | import repacked.com.squareup.javawriter.JavaWriter; 8 | 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_PARCEL; 10 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_SOURCE; 11 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_TARGET; 12 | 13 | /** 14 | * It also handles null values 15 | * 16 | * @author Hannes Dorfmann 17 | */ 18 | public class AbsPrimitiveWrapperCodeGen implements FieldCodeGen { 19 | 20 | private String methodSuffix; 21 | 22 | public AbsPrimitiveWrapperCodeGen(String methodSuffix) { 23 | this.methodSuffix = methodSuffix; 24 | } 25 | 26 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter jw) 27 | throws IOException { 28 | 29 | jw.emitStatement("%s.writeByte( (byte) (%s.%s != null ? 1 : 0) )", CodeGenerator.PARAM_PARCEL, 30 | CodeGenerator.PARAM_SOURCE, field.getFieldName()); 31 | 32 | jw.beginControlFlow("if (%s.%s != null)", CodeGenerator.PARAM_SOURCE, field.getFieldName()); 33 | jw.emitStatement("%s.write%s(%s.%s)", PARAM_PARCEL, methodSuffix, PARAM_SOURCE, 34 | field.getFieldName()); 35 | jw.endControlFlow(); 36 | } 37 | 38 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter jw) 39 | throws IOException { 40 | 41 | jw.emitStatement("boolean %sNullHelper", field.getFieldName()); 42 | jw.emitStatement("%sNullHelper = ( %s.readByte() == 1 )", field.getFieldName(), 43 | CodeGenerator.PARAM_PARCEL); 44 | 45 | jw.beginControlFlow("if (%sNullHelper)", field.getFieldName()); 46 | jw.emitStatement("%s.%s = %s.read%s()", PARAM_TARGET, field.getFieldName(), PARAM_PARCEL, 47 | methodSuffix); 48 | jw.nextControlFlow("else"); 49 | jw.emitStatement("%s.%s = null", PARAM_TARGET, field.getFieldName()); 50 | jw.endControlFlow(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/codegenerator/primitiveswrapper/BooleanWrapperCodeGen.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.codegenerator.primitiveswrapper; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.ParcelableField; 4 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator; 5 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 6 | import java.io.IOException; 7 | import repacked.com.squareup.javawriter.JavaWriter; 8 | 9 | import static com.hannesdorfmann.parcelableplease.processor.codegenerator.CodeGenerator.PARAM_TARGET; 10 | 11 | /** 12 | * It also handles null values 13 | * 14 | * @author Hannes Dorfmann 15 | */ 16 | public class BooleanWrapperCodeGen implements FieldCodeGen { 17 | 18 | @Override public void generateWriteToParcel(ParcelableField field, JavaWriter jw) 19 | throws IOException { 20 | 21 | jw.emitStatement("%s.writeByte( (byte) (%s.%s != null ? 1 : 0) )", CodeGenerator.PARAM_PARCEL, 22 | CodeGenerator.PARAM_SOURCE, field.getFieldName()); 23 | 24 | jw.beginControlFlow("if (%s.%s != null)", CodeGenerator.PARAM_SOURCE, field.getFieldName()); 25 | jw.emitStatement("%s.writeByte( (byte) (%s.%s? 1 : 0))", CodeGenerator.PARAM_PARCEL, 26 | CodeGenerator.PARAM_SOURCE, field.getFieldName()); 27 | jw.endControlFlow(); 28 | } 29 | 30 | @Override public void generateReadFromParcel(ParcelableField field, JavaWriter jw) 31 | throws IOException { 32 | 33 | jw.emitStatement("boolean %sNullHelper", field.getFieldName()); 34 | jw.emitStatement("%sNullHelper = ( %s.readByte() == 1 )", field.getFieldName(), 35 | CodeGenerator.PARAM_PARCEL); 36 | 37 | jw.beginControlFlow("if (%sNullHelper)", field.getFieldName()); 38 | jw.emitStatement("%s.%s = ( %s.readByte() == 1 )", CodeGenerator.PARAM_TARGET, 39 | field.getFieldName(), CodeGenerator.PARAM_PARCEL); 40 | jw.nextControlFlow("else"); 41 | jw.emitStatement("%s.%s = null", PARAM_TARGET, field.getFieldName()); 42 | jw.endControlFlow(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/util/CodeGenInfo.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.util; 2 | 3 | import com.hannesdorfmann.parcelableplease.processor.codegenerator.FieldCodeGen; 4 | import javax.lang.model.type.TypeMirror; 5 | 6 | /** 7 | * A simple class (comparable to a pair) that contains the (see {@link FieldCodeGen} ) and a 8 | * TypeMirror for Generics Type for the same inspected element. Note that Generic Type key can be 9 | * null, or in other words is only available if the inspected element uses generics like 10 | * ArrayList does. In this example the genericsType mirror will be of type Foo. 11 | * 12 | * @author Hannes Dorfmann 13 | */ 14 | public class CodeGenInfo { 15 | 16 | private TypeMirror genericsType; 17 | 18 | private FieldCodeGen generator; 19 | 20 | public CodeGenInfo(FieldCodeGen generator) { 21 | this.generator = generator; 22 | } 23 | 24 | public CodeGenInfo(FieldCodeGen generator, TypeMirror genericsType) { 25 | this.generator = generator; 26 | this.genericsType = genericsType; 27 | } 28 | 29 | public FieldCodeGen getCodeGenerator() { 30 | return generator; 31 | } 32 | 33 | public TypeMirror getGenericsType() { 34 | return genericsType; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /processor/src/main/java/com/hannesdorfmann/parcelableplease/processor/util/TypeUtils.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor.util; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import javax.lang.model.element.Element; 6 | import javax.lang.model.element.PackageElement; 7 | import javax.lang.model.element.TypeElement; 8 | import javax.lang.model.type.TypeMirror; 9 | import javax.lang.model.util.Elements; 10 | 11 | /** 12 | * @author Hannes Dorfmann 13 | */ 14 | public class TypeUtils { 15 | 16 | /** 17 | * Checks if a element is of the type 18 | */ 19 | public static boolean isTypeOf(Element element, Class clazz) { 20 | return isTypeOf(element.asType(), clazz); 21 | } 22 | 23 | /** 24 | * Checks if a TypeMirror equals a class 25 | */ 26 | public static boolean isTypeOf(TypeMirror type, Class clazz) { 27 | return type.toString().equals(clazz.getCanonicalName()); 28 | } 29 | 30 | /** 31 | * Returns the class this element is part of 32 | */ 33 | public static Class isTypeOf(TypeMirror type, List> classList) { 34 | 35 | for (Class c : classList) { 36 | if (isTypeOf(type, c)) { 37 | return c; 38 | } 39 | } 40 | 41 | return null; 42 | } 43 | 44 | /** 45 | * Get the Package name 46 | * 47 | * @throws IOException 48 | */ 49 | public static String getPackageName(Elements elementUtils, TypeElement type) throws IOException { 50 | PackageElement pkg = elementUtils.getPackageOf(type); 51 | if (!pkg.isUnnamed()) { 52 | return pkg.getQualifiedName().toString(); 53 | } else { 54 | return ""; // Default package 55 | } 56 | } 57 | 58 | /** 59 | * Get the qualified name of a class. Also respects innner classes 60 | * 61 | * @param elementUtils Elements 62 | * @param type TypeElement 63 | * @return The qualified name of the class represented by the passed type element 64 | * @throws IOException 65 | */ 66 | public static String getBinaryName(Elements elementUtils, TypeElement type) throws IOException { 67 | String packageName = getPackageName(elementUtils, type); 68 | String qualifiedName = type.getQualifiedName().toString(); 69 | if (packageName.length() > 0) { 70 | return packageName + '.' + qualifiedName.substring(packageName.length() + 1).replace('.', '$'); 71 | } else { 72 | return qualifiedName.replace('.', '$'); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /processor/src/main/java/repacked/com/squareup/javawriter/JavaWriter.java: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Square, Inc. 2 | package repacked.com.squareup.javawriter; 3 | 4 | import java.io.Closeable; 5 | import java.io.IOException; 6 | import java.io.Writer; 7 | import java.lang.annotation.Annotation; 8 | import java.util.ArrayDeque; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.Collection; 12 | import java.util.Collections; 13 | import java.util.Deque; 14 | import java.util.EnumSet; 15 | import java.util.Iterator; 16 | import java.util.LinkedHashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.Map.Entry; 20 | import java.util.Set; 21 | import java.util.TreeSet; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | import javax.lang.model.element.Modifier; 25 | 26 | import static javax.lang.model.element.Modifier.ABSTRACT; 27 | 28 | /** A utility class which aids in generating Java source files. */ 29 | public class JavaWriter implements Closeable { 30 | private static final Pattern TYPE_PATTERN = Pattern.compile("(?:[\\w$]+\\.)*([\\w\\.*$]+)"); 31 | private static final int MAX_SINGLE_LINE_ATTRIBUTES = 3; 32 | private static final String INDENT = " "; 33 | 34 | /** Map fully qualified type names to their short names. */ 35 | private final Map importedTypes = new LinkedHashMap(); 36 | 37 | private String packagePrefix; 38 | private final Deque scopes = new ArrayDeque(); 39 | private final Deque types = new ArrayDeque(); 40 | private final Writer out; 41 | private boolean isCompressingTypes = true; 42 | private String indent = INDENT; 43 | 44 | /** 45 | * @param out the stream to which Java source will be written. This should be a buffered stream. 46 | */ 47 | public JavaWriter(Writer out) { 48 | this.out = out; 49 | } 50 | 51 | public void setCompressingTypes(boolean isCompressingTypes) { 52 | this.isCompressingTypes = isCompressingTypes; 53 | } 54 | 55 | public boolean isCompressingTypes() { 56 | return isCompressingTypes; 57 | } 58 | 59 | public void setIndent(String indent) { 60 | this.indent = indent; 61 | } 62 | 63 | public String getIndent() { 64 | return indent; 65 | } 66 | 67 | /** Emit a package declaration and empty line. */ 68 | public JavaWriter emitPackage(String packageName) throws IOException { 69 | if (this.packagePrefix != null) { 70 | throw new IllegalStateException(); 71 | } 72 | if (packageName.isEmpty()) { 73 | this.packagePrefix = ""; 74 | } else { 75 | out.write("package "); 76 | out.write(packageName); 77 | out.write(";\n\n"); 78 | this.packagePrefix = packageName + "."; 79 | } 80 | return this; 81 | } 82 | 83 | /** 84 | * Emit an import for each {@code type} provided. For the duration of the file, all references to 85 | * these classes will be automatically shortened. 86 | */ 87 | public JavaWriter emitImports(String... types) throws IOException { 88 | return emitImports(Arrays.asList(types)); 89 | } 90 | 91 | /** 92 | * Emit an import for each {@code type} provided. For the duration of the file, all references to 93 | * these classes will be automatically shortened. 94 | */ 95 | public JavaWriter emitImports(Class... types) throws IOException { 96 | List classNames = new ArrayList(types.length); 97 | for (Class classToImport : types) { 98 | classNames.add(classToImport.getName()); 99 | } 100 | return emitImports(classNames); 101 | } 102 | 103 | /** 104 | * Emit an import for each {@code type} in the provided {@code Collection}. For the duration of 105 | * the file, all references to these classes will be automatically shortened. 106 | */ 107 | public JavaWriter emitImports(Collection types) throws IOException { 108 | for (String type : new TreeSet(types)) { 109 | Matcher matcher = TYPE_PATTERN.matcher(type); 110 | if (!matcher.matches()) { 111 | throw new IllegalArgumentException(type); 112 | } 113 | if (importedTypes.put(type, matcher.group(1)) != null) { 114 | throw new IllegalArgumentException(type); 115 | } 116 | out.write("import "); 117 | out.write(type); 118 | out.write(";\n"); 119 | } 120 | return this; 121 | } 122 | 123 | /** 124 | * Emit a static import for each {@code type} provided. For the duration of the file, 125 | * all references to these classes will be automatically shortened. 126 | */ 127 | public JavaWriter emitStaticImports(String... types) throws IOException { 128 | return emitStaticImports(Arrays.asList(types)); 129 | } 130 | 131 | /** 132 | * Emit a static import for each {@code type} in the provided {@code Collection}. For the 133 | * duration of the file, all references to these classes will be automatically shortened. 134 | */ 135 | public JavaWriter emitStaticImports(Collection types) throws IOException { 136 | for (String type : new TreeSet(types)) { 137 | Matcher matcher = TYPE_PATTERN.matcher(type); 138 | if (!matcher.matches()) { 139 | throw new IllegalArgumentException(type); 140 | } 141 | if (importedTypes.put(type, matcher.group(1)) != null) { 142 | throw new IllegalArgumentException(type); 143 | } 144 | out.write("import static "); 145 | out.write(type); 146 | out.write(";\n"); 147 | } 148 | return this; 149 | } 150 | 151 | /** 152 | * Emits a name like {@code java.lang.String} or {@code java.util.List}, 153 | * compressing it with imports if possible. Type compression will only be enabled if 154 | * {@link #isCompressingTypes} is true. 155 | */ 156 | private JavaWriter emitCompressedType(String type) throws IOException { 157 | if (isCompressingTypes) { 158 | out.write(compressType(type)); 159 | } else { 160 | out.write(type); 161 | } 162 | return this; 163 | } 164 | 165 | /** Try to compress a fully-qualified class name to only the class name. */ 166 | public String compressType(String type) { 167 | StringBuilder sb = new StringBuilder(); 168 | if (this.packagePrefix == null) { 169 | throw new IllegalStateException(); 170 | } 171 | 172 | Matcher m = TYPE_PATTERN.matcher(type); 173 | int pos = 0; 174 | while (true) { 175 | boolean found = m.find(pos); 176 | 177 | // Copy non-matching characters like "<". 178 | int typeStart = found ? m.start() : type.length(); 179 | sb.append(type, pos, typeStart); 180 | 181 | if (!found) { 182 | break; 183 | } 184 | 185 | // Copy a single class name, shortening it if possible. 186 | String name = m.group(0); 187 | String imported = importedTypes.get(name); 188 | if (imported != null) { 189 | sb.append(imported); 190 | } else if (isClassInPackage(name, packagePrefix)) { 191 | String compressed = name.substring(packagePrefix.length()); 192 | if (isAmbiguous(compressed)) { 193 | sb.append(name); 194 | } else { 195 | sb.append(compressed); 196 | } 197 | } else if (isClassInPackage(name, "java.lang.")) { 198 | sb.append(name.substring("java.lang.".length())); 199 | } else { 200 | sb.append(name); 201 | } 202 | pos = m.end(); 203 | } 204 | return sb.toString(); 205 | } 206 | 207 | private static boolean isClassInPackage(String name, String packagePrefix) { 208 | if (name.startsWith(packagePrefix)) { 209 | if (name.indexOf('.', packagePrefix.length()) == -1) { 210 | return true; 211 | } 212 | // check to see if the part after the package looks like a class 213 | if (Character.isUpperCase(name.charAt(packagePrefix.length()))) { 214 | return true; 215 | } 216 | } 217 | return false; 218 | } 219 | 220 | /** 221 | * Returns true if the imports contain a class with same simple name as {@code compressed}. 222 | * 223 | * @param compressed simple name of the type 224 | */ 225 | private boolean isAmbiguous(String compressed) { 226 | return importedTypes.values().contains(compressed); 227 | } 228 | 229 | /** 230 | * Emits an initializer declaration. 231 | * 232 | * @param isStatic true if it should be an static initializer, false for an instance initializer. 233 | */ 234 | public JavaWriter beginInitializer(boolean isStatic) throws IOException { 235 | indent(); 236 | if (isStatic) { 237 | out.write("static"); 238 | out.write(" {\n"); 239 | } else { 240 | out.write("{\n"); 241 | } 242 | scopes.push(Scope.INITIALIZER); 243 | return this; 244 | } 245 | 246 | /** Ends the current initializer declaration. */ 247 | public JavaWriter endInitializer() throws IOException { 248 | popScope(Scope.INITIALIZER); 249 | indent(); 250 | out.write("}\n"); 251 | return this; 252 | } 253 | 254 | /** 255 | * Emits a type declaration. 256 | * 257 | * @param kind such as "class", "interface" or "enum". 258 | */ 259 | public JavaWriter beginType(String type, String kind) throws IOException { 260 | return beginType(type, kind, EnumSet.noneOf(Modifier.class), null); 261 | } 262 | 263 | /** 264 | * Emits a type declaration. 265 | * 266 | * @param kind such as "class", "interface" or "enum". 267 | */ 268 | public JavaWriter beginType(String type, String kind, Set modifiers) 269 | throws IOException { 270 | return beginType(type, kind, modifiers, null); 271 | } 272 | 273 | /** 274 | * Emits a type declaration. 275 | * 276 | * @param kind such as "class", "interface" or "enum". 277 | * @param extendsType the class to extend, or null for no extends clause. 278 | */ 279 | public JavaWriter beginType(String type, String kind, Set modifiers, String extendsType, 280 | String... implementsTypes) throws IOException { 281 | indent(); 282 | emitModifiers(modifiers); 283 | out.write(kind); 284 | out.write(" "); 285 | emitCompressedType(type); 286 | if (extendsType != null) { 287 | out.write(" extends "); 288 | emitCompressedType(extendsType); 289 | } 290 | if (implementsTypes.length > 0) { 291 | out.write("\n"); 292 | indent(); 293 | out.write(" implements "); 294 | for (int i = 0; i < implementsTypes.length; i++) { 295 | if (i != 0) { 296 | out.write(", "); 297 | } 298 | emitCompressedType(implementsTypes[i]); 299 | } 300 | } 301 | out.write(" {\n"); 302 | scopes.push("interface".equals(kind) ? Scope.INTERFACE_DECLARATION : Scope.TYPE_DECLARATION); 303 | types.push(type); 304 | return this; 305 | } 306 | 307 | /** Completes the current type declaration. */ 308 | public JavaWriter endType() throws IOException { 309 | popScope(Scope.TYPE_DECLARATION, Scope.INTERFACE_DECLARATION); 310 | types.pop(); 311 | indent(); 312 | out.write("}\n"); 313 | return this; 314 | } 315 | 316 | /** Emits a field declaration. */ 317 | public JavaWriter emitField(String type, String name) throws IOException { 318 | return emitField(type, name, EnumSet.noneOf(Modifier.class), null); 319 | } 320 | 321 | /** Emits a field declaration. */ 322 | public JavaWriter emitField(String type, String name, Set modifiers) 323 | throws IOException { 324 | return emitField(type, name, modifiers, null); 325 | } 326 | 327 | /** Emits a field declaration. */ 328 | public JavaWriter emitField(String type, String name, Set modifiers, 329 | String initialValue) throws IOException { 330 | indent(); 331 | emitModifiers(modifiers); 332 | emitCompressedType(type); 333 | out.write(" "); 334 | out.write(name); 335 | 336 | if (initialValue != null) { 337 | out.write(" ="); 338 | if (!initialValue.startsWith("\n")) { 339 | out.write(" "); 340 | } 341 | 342 | String[] lines = initialValue.split("\n", -1); 343 | out.write(lines[0]); 344 | for (int i = 1; i < lines.length; i++) { 345 | out.write("\n"); 346 | hangingIndent(); 347 | out.write(lines[i]); 348 | } 349 | } 350 | out.write(";\n"); 351 | return this; 352 | } 353 | 354 | /** 355 | * Emit a method declaration. 356 | * 357 | *

A {@code null} return type may be used to indicate a constructor, but 358 | * {@link #beginConstructor(Set, String...)} should be preferred. This behavior may be removed in 359 | * a future release. 360 | * 361 | * @param returnType the method's return type, or null for constructors 362 | * @param name the method name, or the fully qualified class name for constructors. 363 | * @param modifiers the set of modifiers to be applied to the method 364 | * @param parameters alternating parameter types and names. 365 | */ 366 | public JavaWriter beginMethod(String returnType, String name, Set modifiers, 367 | String... parameters) throws IOException { 368 | return beginMethod(returnType, name, modifiers, Arrays.asList(parameters), null); 369 | } 370 | 371 | /** 372 | * Emit a method declaration. 373 | * 374 | *

A {@code null} return type may be used to indicate a constructor, but 375 | * {@link #beginConstructor(Set, List, List)} should be preferred. This behavior may be removed in 376 | * a future release. 377 | * 378 | * @param returnType the method's return type, or null for constructors. 379 | * @param name the method name, or the fully qualified class name for constructors. 380 | * @param modifiers the set of modifiers to be applied to the method 381 | * @param parameters alternating parameter types and names. 382 | * @param throwsTypes the classes to throw, or null for no throws clause. 383 | */ 384 | public JavaWriter beginMethod(String returnType, String name, Set modifiers, 385 | List parameters, List throwsTypes) throws IOException { 386 | indent(); 387 | emitModifiers(modifiers); 388 | if (returnType != null) { 389 | emitCompressedType(returnType); 390 | out.write(" "); 391 | out.write(name); 392 | } else { 393 | emitCompressedType(name); 394 | } 395 | out.write("("); 396 | if (parameters != null) { 397 | for (int p = 0; p < parameters.size();) { 398 | if (p != 0) { 399 | out.write(", "); 400 | } 401 | emitCompressedType(parameters.get(p++)); 402 | out.write(" "); 403 | emitCompressedType(parameters.get(p++)); 404 | } 405 | } 406 | out.write(")"); 407 | if (throwsTypes != null && throwsTypes.size() > 0) { 408 | out.write("\n"); 409 | indent(); 410 | out.write(" throws "); 411 | for (int i = 0; i < throwsTypes.size(); i++) { 412 | if (i != 0) { 413 | out.write(", "); 414 | } 415 | emitCompressedType(throwsTypes.get(i)); 416 | } 417 | } 418 | if (modifiers.contains(ABSTRACT) || Scope.INTERFACE_DECLARATION.equals(scopes.peek())) { 419 | out.write(";\n"); 420 | scopes.push(Scope.ABSTRACT_METHOD); 421 | } else { 422 | out.write(" {\n"); 423 | scopes.push(returnType == null ? Scope.CONSTRUCTOR : Scope.NON_ABSTRACT_METHOD); 424 | } 425 | return this; 426 | } 427 | 428 | public JavaWriter beginConstructor(Set modifiers, String... parameters) 429 | throws IOException { 430 | beginMethod(null, rawType(types.peekFirst()), modifiers, parameters); 431 | return this; 432 | } 433 | 434 | public JavaWriter beginConstructor(Set modifiers, 435 | List parameters, List throwsTypes) 436 | throws IOException { 437 | beginMethod(null, rawType(types.peekFirst()), modifiers, parameters, throwsTypes); 438 | return this; 439 | } 440 | 441 | /** Emits some Javadoc comments with line separated by {@code \n}. */ 442 | public JavaWriter emitJavadoc(String javadoc, Object... params) throws IOException { 443 | String formatted = String.format(javadoc, params); 444 | 445 | indent(); 446 | out.write("/**\n"); 447 | for (String line : formatted.split("\n")) { 448 | indent(); 449 | out.write(" *"); 450 | if (!line.isEmpty()) { 451 | out.write(" "); 452 | out.write(line); 453 | } 454 | out.write("\n"); 455 | } 456 | indent(); 457 | out.write(" */\n"); 458 | return this; 459 | } 460 | 461 | /** Emits a single line comment. */ 462 | public JavaWriter emitSingleLineComment(String comment, Object... args) throws IOException { 463 | indent(); 464 | out.write("// "); 465 | out.write(String.format(comment, args)); 466 | out.write("\n"); 467 | return this; 468 | } 469 | 470 | public JavaWriter emitEmptyLine() throws IOException { 471 | out.write("\n"); 472 | return this; 473 | } 474 | 475 | public JavaWriter emitEnumValue(String name) throws IOException { 476 | indent(); 477 | out.write(name); 478 | out.write(",\n"); 479 | return this; 480 | } 481 | 482 | /** 483 | * A simple switch to emit the proper enum depending if its last causing it to be terminated 484 | * by a semi-colon ({@code ;}). 485 | */ 486 | public JavaWriter emitEnumValue(String name, boolean isLast) throws IOException { 487 | return isLast ? emitLastEnumValue(name) : emitEnumValue(name); 488 | } 489 | 490 | private JavaWriter emitLastEnumValue(String name) throws IOException { 491 | indent(); 492 | out.write(name); 493 | out.write(";\n"); 494 | return this; 495 | } 496 | 497 | /** Emit a list of enum values followed by a semi-colon ({@code ;}). */ 498 | public JavaWriter emitEnumValues(Iterable names) throws IOException { 499 | Iterator iterator = names.iterator(); 500 | 501 | while (iterator.hasNext()) { 502 | String name = iterator.next(); 503 | if (iterator.hasNext()) { 504 | emitEnumValue(name); 505 | } else { 506 | emitLastEnumValue(name); 507 | } 508 | } 509 | 510 | return this; 511 | } 512 | 513 | /** Equivalent to {@code annotation(annotation, emptyMap())}. */ 514 | public JavaWriter emitAnnotation(String annotation) throws IOException { 515 | return emitAnnotation(annotation, Collections.emptyMap()); 516 | } 517 | 518 | /** Equivalent to {@code annotation(annotationType.getName(), emptyMap())}. */ 519 | public JavaWriter emitAnnotation(Class annotationType) throws IOException { 520 | return emitAnnotation(type(annotationType), Collections.emptyMap()); 521 | } 522 | 523 | /** 524 | * Annotates the next element with {@code annotationType} and a {@code value}. 525 | * 526 | * @param value an object used as the default (value) parameter of the annotation. The value will 527 | * be encoded using Object.toString(); use {@link #stringLiteral} for String values. Object 528 | * arrays are written one element per line. 529 | */ 530 | public JavaWriter emitAnnotation(Class annotationType, Object value) 531 | throws IOException { 532 | return emitAnnotation(type(annotationType), value); 533 | } 534 | 535 | /** 536 | * Annotates the next element with {@code annotation} and a {@code value}. 537 | * 538 | * @param value an object used as the default (value) parameter of the annotation. The value will 539 | * be encoded using Object.toString(); use {@link #stringLiteral} for String values. Object 540 | * arrays are written one element per line. 541 | */ 542 | public JavaWriter emitAnnotation(String annotation, Object value) throws IOException { 543 | indent(); 544 | out.write("@"); 545 | emitCompressedType(annotation); 546 | out.write("("); 547 | emitAnnotationValue(value); 548 | out.write(")"); 549 | out.write("\n"); 550 | return this; 551 | } 552 | 553 | /** Equivalent to {@code annotation(annotationType.getName(), attributes)}. */ 554 | public JavaWriter emitAnnotation(Class annotationType, 555 | Map attributes) throws IOException { 556 | return emitAnnotation(type(annotationType), attributes); 557 | } 558 | 559 | /** 560 | * Annotates the next element with {@code annotation} and {@code attributes}. 561 | * 562 | * @param attributes a map from annotation attribute names to their values. Values are encoded 563 | * using Object.toString(); use {@link #stringLiteral} for String values. Object arrays are 564 | * written one element per line. 565 | */ 566 | public JavaWriter emitAnnotation(String annotation, Map attributes) 567 | throws IOException { 568 | indent(); 569 | out.write("@"); 570 | emitCompressedType(annotation); 571 | switch (attributes.size()) { 572 | case 0: 573 | break; 574 | case 1: 575 | Entry onlyEntry = attributes.entrySet().iterator().next(); 576 | out.write("("); 577 | if (!"value".equals(onlyEntry.getKey())) { 578 | out.write(onlyEntry.getKey()); 579 | out.write(" = "); 580 | } 581 | emitAnnotationValue(onlyEntry.getValue()); 582 | out.write(")"); 583 | break; 584 | default: 585 | boolean split = attributes.size() > MAX_SINGLE_LINE_ATTRIBUTES 586 | || containsArray(attributes.values()); 587 | out.write("("); 588 | scopes.push(Scope.ANNOTATION_ATTRIBUTE); 589 | String separator = split ? "\n" : ""; 590 | for (Entry entry : attributes.entrySet()) { 591 | out.write(separator); 592 | separator = split ? ",\n" : ", "; 593 | if (split) { 594 | indent(); 595 | } 596 | out.write(entry.getKey()); 597 | out.write(" = "); 598 | Object value = entry.getValue(); 599 | emitAnnotationValue(value); 600 | } 601 | popScope(Scope.ANNOTATION_ATTRIBUTE); 602 | if (split) { 603 | out.write("\n"); 604 | indent(); 605 | } 606 | out.write(")"); 607 | break; 608 | } 609 | out.write("\n"); 610 | return this; 611 | } 612 | 613 | private boolean containsArray(Collection values) { 614 | for (Object value : values) { 615 | if (value instanceof Object[]) { 616 | return true; 617 | } 618 | } 619 | return false; 620 | } 621 | 622 | /** 623 | * Writes a single annotation value. If the value is an array, each element in the array will be 624 | * written to its own line. 625 | */ 626 | private JavaWriter emitAnnotationValue(Object value) throws IOException { 627 | if (value instanceof Object[]) { 628 | out.write("{"); 629 | boolean firstValue = true; 630 | scopes.push(Scope.ANNOTATION_ARRAY_VALUE); 631 | for (Object o : ((Object[]) value)) { 632 | if (firstValue) { 633 | firstValue = false; 634 | out.write("\n"); 635 | } else { 636 | out.write(",\n"); 637 | } 638 | indent(); 639 | out.write(o.toString()); 640 | } 641 | popScope(Scope.ANNOTATION_ARRAY_VALUE); 642 | out.write("\n"); 643 | indent(); 644 | out.write("}"); 645 | } else { 646 | out.write(value.toString()); 647 | } 648 | return this; 649 | } 650 | 651 | /** 652 | * @param pattern a code pattern like "int i = %s". Newlines will be further indented. Should not 653 | * contain trailing semicolon. 654 | */ 655 | public JavaWriter emitStatement(String pattern, Object... args) throws IOException { 656 | checkInMethod(); 657 | String[] lines = String.format(pattern, args).split("\n", -1); 658 | indent(); 659 | out.write(lines[0]); 660 | for (int i = 1; i < lines.length; i++) { 661 | out.write("\n"); 662 | hangingIndent(); 663 | out.write(lines[i]); 664 | } 665 | out.write(";\n"); 666 | return this; 667 | } 668 | 669 | /** 670 | * @param controlFlow the control flow construct and its code, such as "if (foo == 5)". Shouldn't 671 | * contain braces or newline characters. 672 | */ 673 | public JavaWriter beginControlFlow(String controlFlow, Object... args) throws IOException { 674 | checkInMethod(); 675 | indent(); 676 | out.write(String.format(controlFlow, args)); 677 | out.write(" {\n"); 678 | scopes.push(Scope.CONTROL_FLOW); 679 | return this; 680 | } 681 | 682 | /** 683 | * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)". 684 | * Shouldn't contain braces or newline characters. 685 | */ 686 | public JavaWriter nextControlFlow(String controlFlow, Object... args) throws IOException { 687 | popScope(Scope.CONTROL_FLOW); 688 | indent(); 689 | scopes.push(Scope.CONTROL_FLOW); 690 | out.write("} "); 691 | out.write(String.format(controlFlow, args)); 692 | out.write(" {\n"); 693 | return this; 694 | } 695 | 696 | public JavaWriter endControlFlow() throws IOException { 697 | return endControlFlow(null); 698 | } 699 | 700 | /** 701 | * @param controlFlow the optional control flow construct and its code, such as 702 | * "while(foo == 20)". Only used for "do/while" control flows. 703 | */ 704 | public JavaWriter endControlFlow(String controlFlow, Object... args) throws IOException { 705 | popScope(Scope.CONTROL_FLOW); 706 | indent(); 707 | if (controlFlow != null) { 708 | out.write("} "); 709 | out.write(String.format(controlFlow, args)); 710 | out.write(";\n"); 711 | } else { 712 | out.write("}\n"); 713 | } 714 | return this; 715 | } 716 | 717 | /** Completes the current method declaration. */ 718 | public JavaWriter endMethod() throws IOException { 719 | Scope popped = scopes.pop(); 720 | // support calling a constructor a "method" to support the legacy code 721 | if (popped == Scope.NON_ABSTRACT_METHOD || popped == Scope.CONSTRUCTOR) { 722 | indent(); 723 | out.write("}\n"); 724 | } else if (popped != Scope.ABSTRACT_METHOD) { 725 | throw new IllegalStateException(); 726 | } 727 | return this; 728 | } 729 | 730 | /** Completes the current constructor declaration. */ 731 | public JavaWriter endConstructor() throws IOException { 732 | popScope(Scope.CONSTRUCTOR); 733 | indent(); 734 | out.write("}\n"); 735 | return this; 736 | } 737 | 738 | /** Returns the string literal representing {@code data}, including wrapping quotes. */ 739 | public static String stringLiteral(String data) { 740 | StringBuilder result = new StringBuilder(); 741 | result.append('"'); 742 | for (int i = 0; i < data.length(); i++) { 743 | char c = data.charAt(i); 744 | switch (c) { 745 | case '"': 746 | result.append("\\\""); 747 | break; 748 | case '\\': 749 | result.append("\\\\"); 750 | break; 751 | case '\b': 752 | result.append("\\b"); 753 | break; 754 | case '\t': 755 | result.append("\\t"); 756 | break; 757 | case '\n': 758 | result.append("\\n"); 759 | break; 760 | case '\f': 761 | result.append("\\f"); 762 | break; 763 | case '\r': 764 | result.append("\\r"); 765 | break; 766 | default: 767 | if (Character.isISOControl(c)) { 768 | result.append(String.format("\\u%04x", (int) c)); 769 | } else { 770 | result.append(c); 771 | } 772 | } 773 | } 774 | result.append('"'); 775 | return result.toString(); 776 | } 777 | 778 | /** Build a string representation of a type and optionally its generic type arguments. */ 779 | public static String type(Class raw, String... parameters) { 780 | if (parameters.length == 0) { 781 | return raw.getCanonicalName(); 782 | } 783 | if (raw.getTypeParameters().length != parameters.length) { 784 | throw new IllegalArgumentException(); 785 | } 786 | StringBuilder result = new StringBuilder(); 787 | result.append(raw.getCanonicalName()); 788 | result.append("<"); 789 | result.append(parameters[0]); 790 | for (int i = 1; i < parameters.length; i++) { 791 | result.append(", "); 792 | result.append(parameters[i]); 793 | } 794 | result.append(">"); 795 | return result.toString(); 796 | } 797 | 798 | /** Build a string representation of the raw type for a (optionally generic) type. */ 799 | public static String rawType(String type) { 800 | int lessThanIndex = type.indexOf('<'); 801 | if (lessThanIndex != -1) { 802 | return type.substring(0, lessThanIndex); 803 | } 804 | return type; 805 | } 806 | 807 | @Override public void close() throws IOException { 808 | out.close(); 809 | } 810 | 811 | /** Emits the modifiers to the writer. */ 812 | private void emitModifiers(Set modifiers) throws IOException { 813 | if (modifiers.isEmpty()) { 814 | return; 815 | } 816 | // Use an EnumSet to ensure the proper ordering 817 | if (!(modifiers instanceof EnumSet)) { 818 | modifiers = EnumSet.copyOf(modifiers); 819 | } 820 | for (Modifier modifier : modifiers) { 821 | out.append(modifier.toString()).append(' '); 822 | } 823 | } 824 | 825 | private void indent() throws IOException { 826 | for (int i = 0, count = scopes.size(); i < count; i++) { 827 | out.write(indent); 828 | } 829 | } 830 | 831 | private void hangingIndent() throws IOException { 832 | for (int i = 0, count = scopes.size() + 2; i < count; i++) { 833 | out.write(indent); 834 | } 835 | } 836 | 837 | private static final EnumSet 838 | METHOD_SCOPES = EnumSet.of(Scope.NON_ABSTRACT_METHOD, Scope.CONSTRUCTOR, Scope.CONTROL_FLOW, 839 | Scope.INITIALIZER); 840 | 841 | private void checkInMethod() { 842 | if (!METHOD_SCOPES.contains(scopes.peekFirst())) { 843 | throw new IllegalArgumentException(); 844 | } 845 | } 846 | 847 | private void popScope(Scope... expected) { 848 | if (!EnumSet.copyOf(Arrays.asList(expected)).contains(scopes.pop())) { 849 | throw new IllegalStateException(); 850 | } 851 | } 852 | 853 | private enum Scope { 854 | TYPE_DECLARATION, 855 | INTERFACE_DECLARATION, 856 | ABSTRACT_METHOD, 857 | NON_ABSTRACT_METHOD, 858 | CONSTRUCTOR, 859 | CONTROL_FLOW, 860 | ANNOTATION_ATTRIBUTE, 861 | ANNOTATION_ARRAY_VALUE, 862 | INITIALIZER 863 | } 864 | } 865 | -------------------------------------------------------------------------------- /processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | com.hannesdorfmann.parcelableplease.processor.ParcelablePleaseProcessor -------------------------------------------------------------------------------- /processor/src/test/java/com/hannesdorfmann/parcelableplease/processor/ParcelablePleaseProcessorTest.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.processor; 2 | 3 | import com.google.common.truth.Truth; 4 | import com.google.testing.compile.JavaFileObjects; 5 | import com.google.testing.compile.JavaSourceSubjectFactory; 6 | import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease; 7 | import javax.tools.JavaFileObject; 8 | import org.junit.Test; 9 | 10 | /** 11 | * @author Hannes Dorfmann 12 | */ 13 | public class ParcelablePleaseProcessorTest { 14 | 15 | @Test 16 | public void abstractClass() { 17 | 18 | String annotation = ParcelablePlease.class.getCanonicalName(); 19 | JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.AbstractClass", 20 | "package test;", 21 | "", 22 | "@" + annotation, 23 | "abstract class AbstractClass {}"); 24 | 25 | Truth.assertAbout(JavaSourceSubjectFactory.javaSource()) 26 | .that(componentFile).processedWith(new ParcelablePleaseProcessor()) 27 | .failsToCompile() 28 | .withErrorContaining("Element AbstractClass is annotated with @ParcelablePlease but is an abstract class. Abstract classes can not be annotated. Annotate the concrete class that implements all abstract methods with @ParcelablePlease"); 29 | 30 | } 31 | 32 | @Test 33 | public void simpleClass() { 34 | 35 | String annotation = ParcelablePlease.class.getCanonicalName(); 36 | JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.SimpleClass", 37 | "package test;", 38 | "", 39 | "@" + annotation, 40 | "class SimpleClass {" + 41 | "int id;" + 42 | "String name;" + 43 | "}"); 44 | 45 | Truth.assertAbout(JavaSourceSubjectFactory.javaSource()) 46 | .that(componentFile).processedWith(new ParcelablePleaseProcessor()) 47 | .compilesWithoutError(); 48 | } 49 | 50 | 51 | @Test 52 | public void innerClass() { 53 | 54 | String annotation = ParcelablePlease.class.getCanonicalName(); 55 | JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.OuterClass", 56 | "package test;", 57 | "", 58 | "class OuterClass {", 59 | "@" + annotation, 60 | "class InnerClass {", 61 | "int id;", 62 | "String name;" , 63 | "}", 64 | "}"); 65 | 66 | Truth.assertAbout(JavaSourceSubjectFactory.javaSource()) 67 | .that(componentFile).processedWith(new ParcelablePleaseProcessor()) 68 | .compilesWithoutError(); 69 | } 70 | 71 | @Test 72 | public void stringList() { 73 | 74 | String annotation = ParcelablePlease.class.getCanonicalName(); 75 | JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.StringListTest", 76 | "package test;", 77 | "", 78 | "@" + annotation, 79 | "class StringListTest {", 80 | " java.util.List strings;", 81 | "}"); 82 | 83 | Truth.assertAbout(JavaSourceSubjectFactory.javaSource()) 84 | .that(componentFile).processedWith(new ParcelablePleaseProcessor()) 85 | .compilesWithoutError(); 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | -------------------------------------------------------------------------------- /sample/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.neenbedankt.android-apt' 3 | 4 | android { 5 | compileSdkVersion 21 6 | buildToolsVersion "21.1.2" 7 | 8 | defaultConfig { 9 | applicationId "com.hannesdorfmann.parcelableplease.sample" 10 | minSdkVersion 8 11 | targetSdkVersion 21 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | def version = '1.0.2-SNAPSHOT' 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | compile 'com.android.support:appcompat-v7:21.0.3' 28 | 29 | apt 'com.hannesdorfmann.parcelableplease:processor:' + version 30 | compile 'com.hannesdorfmann.parcelableplease:annotation:' + version 31 | compile 'com.jakewharton:butterknife:6.0.0' 32 | } 33 | -------------------------------------------------------------------------------- /sample/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/hannes/android-sdks/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /sample/app/src/main/java/com/hannesdorfmann/parcelableplease/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.ActionBarActivity; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | import android.widget.TextView; 8 | import butterknife.ButterKnife; 9 | import butterknife.InjectView; 10 | import butterknife.OnClick; 11 | import com.hannesdorfmann.parcelableplease.sample.model.Banana; 12 | import com.hannesdorfmann.parcelableplease.sample.model.Fruit; 13 | import com.hannesdorfmann.parcelableplease.sample.model.Orange; 14 | import java.util.ArrayList; 15 | 16 | public class MainActivity extends ActionBarActivity { 17 | 18 | private final String KEY_FRUIT = "fruit"; 19 | 20 | @InjectView(R.id.selectedFruit) TextView selectedFruitTextView; 21 | 22 | Fruit selectedFruit; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_main); 28 | ButterKnife.inject(this); 29 | 30 | if (savedInstanceState != null){ 31 | selectedFruit = savedInstanceState.getParcelable(KEY_FRUIT); 32 | } 33 | 34 | updateUi(); 35 | } 36 | 37 | @Override protected void onSaveInstanceState(Bundle outState) { 38 | super.onSaveInstanceState(outState); 39 | outState.putParcelable(KEY_FRUIT, selectedFruit); 40 | } 41 | 42 | @Override 43 | public boolean onCreateOptionsMenu(Menu menu) { 44 | // Inflate the menu; this adds items to the action bar if it is present. 45 | getMenuInflater().inflate(R.menu.menu_main, menu); 46 | return true; 47 | } 48 | 49 | @Override 50 | public boolean onOptionsItemSelected(MenuItem item) { 51 | // Handle action bar item clicks here. The action bar will 52 | // automatically handle clicks on the Home/Up button, so long 53 | // as you specify a parent activity in AndroidManifest.xml. 54 | int id = item.getItemId(); 55 | 56 | //noinspection SimplifiableIfStatement 57 | if (id == R.id.action_settings) { 58 | return true; 59 | } 60 | 61 | return super.onOptionsItemSelected(item); 62 | } 63 | 64 | @OnClick(R.id.banana) 65 | public void onBananaClicked() { 66 | selectedFruit = new Banana(); 67 | updateUi(); 68 | } 69 | 70 | @OnClick(R.id.orange) 71 | public void onOrangeClicked() { 72 | Orange orange = new Orange(); 73 | orange.content = new ArrayList<>(); 74 | orange.content.add("First"); 75 | orange.content.add("Second"); 76 | selectedFruit = orange; 77 | updateUi(); 78 | } 79 | 80 | private void updateUi() { 81 | String fruit = ""; 82 | if (selectedFruit != null) { 83 | fruit = selectedFruit.toString(); 84 | } 85 | selectedFruitTextView.setText(fruit); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /sample/app/src/main/java/com/hannesdorfmann/parcelableplease/sample/model/Banana.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.sample.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease; 6 | 7 | /** 8 | * @author Hannes Dorfmann 9 | */ 10 | @ParcelablePlease public class Banana extends Fruit implements Parcelable { 11 | 12 | public Banana() { 13 | super("Banana", FruitType.B); 14 | } 15 | 16 | 17 | @Override public int describeContents() { 18 | return 0; 19 | } 20 | 21 | @Override public void writeToParcel(Parcel dest, int flags) { 22 | BananaParcelablePlease.writeToParcel(this, dest, flags); 23 | } 24 | 25 | public static final Creator CREATOR = new Creator() { 26 | public Banana createFromParcel(Parcel source) { 27 | Banana target = new Banana(); 28 | BananaParcelablePlease.readFromParcel(target, source); 29 | return target; 30 | } 31 | 32 | public Banana[] newArray(int size) { 33 | return new Banana[size]; 34 | } 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /sample/app/src/main/java/com/hannesdorfmann/parcelableplease/sample/model/Fruit.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.sample.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import com.hannesdorfmann.parcelableplease.annotation.Bagger; 6 | import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease; 7 | 8 | /** 9 | * @author Hannes Dorfmann 10 | */ 11 | @ParcelablePlease public class Fruit implements Parcelable { 12 | 13 | String name; 14 | 15 | @Bagger(FruitTypeBagger.class) FruitType type; 16 | 17 | public Fruit(String name, FruitType type) { 18 | this.name = name; 19 | this.type = type; 20 | } 21 | 22 | protected Fruit() { 23 | 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public FruitType getType() { 31 | return type; 32 | } 33 | 34 | @Override public String toString() { 35 | return name + "(" + type + ")"; 36 | } 37 | 38 | @Override public int describeContents() { 39 | return 0; 40 | } 41 | 42 | @Override public void writeToParcel(Parcel dest, int flags) { 43 | FruitParcelablePlease.writeToParcel(this, dest, flags); 44 | } 45 | 46 | public static final Creator CREATOR = new Creator() { 47 | public Fruit createFromParcel(Parcel source) { 48 | Fruit target = new Fruit(); 49 | FruitParcelablePlease.readFromParcel(target, source); 50 | return target; 51 | } 52 | 53 | public Fruit[] newArray(int size) { 54 | return new Fruit[size]; 55 | } 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /sample/app/src/main/java/com/hannesdorfmann/parcelableplease/sample/model/FruitType.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.sample.model; 2 | 3 | /** 4 | * @author Hannes Dorfmann 5 | */ 6 | public enum FruitType { 7 | 8 | A, 9 | B, 10 | C 11 | 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /sample/app/src/main/java/com/hannesdorfmann/parcelableplease/sample/model/FruitTypeBagger.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.sample.model; 2 | 3 | import android.os.Parcel; 4 | import com.hannesdorfmann.parcelableplease.ParcelBagger; 5 | 6 | /** 7 | * @author Hannes Dorfmann 8 | */ 9 | public class FruitTypeBagger implements ParcelBagger { 10 | 11 | @Override public void write(FruitType value, Parcel out, int flags) { 12 | out.writeString(value.toString()); 13 | } 14 | 15 | @Override public FruitType read(Parcel in) { 16 | return FruitType.valueOf(in.readString()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/app/src/main/java/com/hannesdorfmann/parcelableplease/sample/model/Orange.java: -------------------------------------------------------------------------------- 1 | package com.hannesdorfmann.parcelableplease.sample.model; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease; 6 | import java.util.List; 7 | 8 | /** 9 | * @author Hannes Dorfmann 10 | */ 11 | @ParcelablePlease public class Orange extends Fruit implements Parcelable { 12 | 13 | public Orange() { 14 | super("Orange", FruitType.C); 15 | } 16 | 17 | public List content; 18 | 19 | @Override public int describeContents() { 20 | return 0; 21 | } 22 | 23 | @Override public void writeToParcel(Parcel dest, int flags) { 24 | OrangeParcelablePlease.writeToParcel(this, dest, flags); 25 | } 26 | 27 | public static final Creator CREATOR = new Creator() { 28 | public Orange createFromParcel(Parcel source) { 29 | Orange target = new Orange(); 30 | OrangeParcelablePlease.readFromParcel(target, source); 31 | return target; 32 | } 33 | 34 | public Orange[] newArray(int size) { 35 | return new Orange[size]; 36 | } 37 | }; 38 | 39 | @Override public String toString() { 40 | return super.toString() + " "+content; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sample/app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockeqwe/ParcelablePlease/4bcb6695ade8cb0ec60af5b137b60f751262086c/sample/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockeqwe/ParcelablePlease/4bcb6695ade8cb0ec60af5b137b60f751262086c/sample/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockeqwe/ParcelablePlease/4bcb6695ade8cb0ec60af5b137b60f751262086c/sample/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockeqwe/ParcelablePlease/4bcb6695ade8cb0ec60af5b137b60f751262086c/sample/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 | 22 |