├── .circleci └── config.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ └── maven.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── code_of_conduct.md ├── pom.xml ├── pull_request_template.md └── src └── main ├── java └── io │ └── github │ └── pakistancan │ └── codable │ ├── annotation │ ├── Codable.java │ └── IgnoreProperty.java │ ├── model │ ├── ObjectType.java │ └── TypeInfo.java │ └── processor │ └── CodableGenerator.java └── resources └── META-INF └── services └── javax.annotation.processing.Processor /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Use the latest 2.1 version of CircleCI pipeline process engine. 2 | # See: https://circleci.com/docs/2.0/configuration-reference 3 | version: 2.1 4 | 5 | # Define a job to be invoked later in a workflow. 6 | # See: https://circleci.com/docs/2.0/configuration-reference/#jobs 7 | jobs: 8 | # Below is the definition of your job to build and test your app, you can rename and customize it as you want. 9 | build-and-test: 10 | # These next lines define a Docker executor: https://circleci.com/docs/2.0/executor-types/ 11 | # You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub. 12 | # Be sure to update the Docker image tag below to openjdk version of your application. 13 | # A list of available CircleCI Docker Convenience Images are available here: https://circleci.com/developer/images/image/cimg/openjdk 14 | docker: 15 | - image: cimg/openjdk:11.0 16 | # Add steps to the job 17 | # See: https://circleci.com/docs/2.0/configuration-reference/#steps 18 | steps: 19 | # Checkout the code as the first step. 20 | - checkout 21 | # Use mvn clean and package as the standard maven build phase 22 | - run: 23 | name: Build 24 | command: mvn -B -DskipTests clean package 25 | # Then run your tests! 26 | - run: 27 | name: Test 28 | command: mvn test 29 | 30 | # Invoke jobs via workflows 31 | # See: https://circleci.com/docs/2.0/configuration-reference/#workflows 32 | workflows: 33 | sample: # This is the name of the workflow, feel free to change it to better match your workflow. 34 | # Inside the workflow, you define the jobs you want to run. 35 | jobs: 36 | - build-and-test 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: pakistancan 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Input file caused problem 16 | 2. Error log or trace if any 17 | 3. Output file if generated 18 | 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Version Info (please complete the following information):** 27 | - Java version 28 | - Version [e.g. 22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement, help wanted 6 | assignees: pakistancan 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '28 8 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up JDK 11 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: '11' 23 | distribution: 'temurin' 24 | cache: maven 25 | - name: Build with Maven 26 | run: mvn -B package --file pom.xml 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings/ 4 | target/ 5 | .idea 6 | pom.xml.tag 7 | pom.xml.releaseBackup 8 | pom.xml.versionsBackup 9 | pom.xml.next 10 | release.properties 11 | dependency-reduced-pom.xml 12 | buildNumber.properties 13 | .mvn/timing.properties 14 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 15 | .mvn/wrapper/maven-wrapper.jar 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the java-codable 2 | 3 | First off, thank you for taking the time to contribute! :+1: :tada: 4 | 5 | ### Table of Contents 6 | 7 | * [Code of Conduct](#code-of-conduct) 8 | * [How to Contribute](#how-to-contribute) 9 | * [Ask questions](#ask-questions) 10 | * [Create an Issue](#create-an-issue) 11 | * [Issue Lifecycle](#issue-lifecycle) 12 | * [Submit a Pull Request](#submit-a-pull-request) 13 | * [Participate in Reviews](#participate-in-reviews) 14 | 15 | 16 | ### Code of Conduct 17 | 18 | This project is governed by the [Code of Conduct](code_of_conduct.md). 19 | By participating you are expected to uphold this code. 20 | Please report unacceptable behavior. 21 | 22 | ### How to Contribute 23 | 24 | #### Ask questions 25 | 26 | If you believe there is an issue, search through 27 | [existing issues](https://github.com/pakistancan/java-codable/issues) trying a 28 | few different ways to find discussions, past or current, that are related to the issue. 29 | Reading those discussions helps you to learn about the issue, and helps us to make a 30 | decision. 31 | 32 | 33 | #### Create an Issue 34 | 35 | Reporting an issue or making a feature request is a great way to contribute. Your feedback 36 | and the conversations that result from it provide a continuous flow of ideas. However, 37 | before creating a ticket, please take the time to [ask and research](#ask-questions) first. 38 | 39 | If you create an issue after a discussion on Stack Overflow, please provide a description 40 | in the issue instead of simply referring to Stack Overflow. The issue tracker is an 41 | important place of record for design discussions and should be self-sufficient. 42 | 43 | Once you're ready, create an issue on [GitHub](https://github.com/pakistancan/java-codable/issues). 44 | 45 | Many issues are caused by subtle behavior, typos, and unintended configuration. 46 | Creating a [Minimal Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) 47 | of the problem helps the team 48 | quickly triage your issue and get to the core of the problem. 49 | 50 | #### Issue Lifecycle 51 | 52 | When an issue is first created, it is flagged `waiting-for-triage` waiting for a team 53 | member to triage it. Once the issue has been reviewed, the team may ask for further 54 | information if needed, and based on the findings, the issue is either assigned a target 55 | milestone or is closed with a specific status. 56 | 57 | When a fix is ready, the issue is closed and may still be re-opened until the fix is 58 | released. After that the issue will typically no longer be reopened. In rare cases if the 59 | issue was not at all fixed, the issue may be re-opened. In most cases however any 60 | follow-up reports will need to be created as new issues with a fresh description. 61 | 62 | #### Submit a Pull Request 63 | 64 | 1. Should you create an issue first? No, just create the pull request and use the 65 | description to provide context and motivation, as you would for an issue. If you want 66 | to start a discussion first or have already created an issue, once a pull request is 67 | created, we will close the issue as superseded by the pull request, and the discussion 68 | about the issue will continue under the pull request. 69 | 70 | 1. Always check out the `main` branch and submit pull requests against it 71 | (for target version see [pom.xml](pom.xml)). 72 | Backports to prior versions will be considered on a case-by-case basis and reflected as 73 | the fix version in the issue tracker. 74 | 75 | 1. Choose the granularity of your commits consciously and squash commits that represent 76 | multiple edits or corrections of the same logical change. See 77 | [Rewriting History section of Pro Git](https://git-scm.com/book/en/Git-Tools-Rewriting-History) 78 | for an overview of streamlining the commit history. 79 | 80 | 1. Format commit messages using 55 characters for the subject line, 72 characters per line 81 | for the description, followed by the issue fixed, e.g. `Closes gh-22276`. See the 82 | [Commit Guidelines section of Pro Git](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines) 83 | for best practices around commit messages, and use `git log` to see some examples. 84 | 85 | 1. If there is a prior issue, reference the GitHub issue number in the description of the 86 | pull request. 87 | 88 | If accepted, your contribution may be heavily modified as needed prior to merging. 89 | You will likely retain author attribution for your Git commits granted that the bulk of 90 | your changes remain intact. You may also be asked to rework the submission. 91 | 92 | If asked to make corrections, simply push the changes against the same branch, and your 93 | pull request will be updated. In other words, you do not need to create a new pull request 94 | when asked to make changes. 95 | 96 | #### Participate in Reviews 97 | 98 | Helping to review pull requests is another great way to contribute. Your feedback 99 | can help to shape the implementation of new features. When reviewing pull requests, 100 | however, please refrain from approving or rejecting a PR unless you are a core 101 | committer for the java-codable. 102 | 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ali 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Java CI with Maven](https://github.com/pakistancan/java-codable/actions/workflows/maven.yml/badge.svg)](https://github.com/pakistancan/java-codable/actions/workflows/maven.yml) [![CircleCI](https://dl.circleci.com/status-badge/img/gh/pakistancan/java-codable/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/pakistancan/java-codable/tree/main) 2 | 3 | # java-codable 4 | java-codable is a Java beans to swift codable converter, it is java compile plugin, you need to add this your maven configuration to get it working. 5 | 6 | ## Configuration 7 | 8 | Add codeable to project dependencies 9 | 10 | ```xml 11 | 12 | io.github.pakistancan 13 | codable-converter 14 | 1.0.2 15 | 16 | ``` 17 | 18 | ## Compiler configuration 19 | 20 | ```xml 21 | 22 | src/main/java 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-compiler-plugin 27 | 3.11.0 28 | 29 | 1.8 30 | 1.8 31 | 32 | io.github.pakistancan.codable.processor.CodableGenerator 33 | 34 | true 35 | 36 | 37 | ./generated/ 38 | com.alix. 39 | open 40 | 41 | 42 | true 43 | true 44 | 45 | 46 | 47 | 48 | ``` 49 | 50 | It will convert classes referenced from `com.alix.` and all of its sub-packages, output will be generated in `/generated/swift`, if package contains `.request.` then it'll generate those classes in REQ subfolder, otherwise in RES sub-folder. 51 | 52 | Enums will be generated in `Enums` sub-folder, if there is any date referenced in any class, Formatters class will be generated in `Formatter/` sub-folder 53 | 54 | If you are using gradle, it should be as simple as adding anootation processor to dependencies section of your build.gradle file 55 | 56 | ```gradle 57 | annotationProcessor 'io.github.pakistancan:codable-converter:1.0.3' 58 | ``` 59 | 60 | compiler arguments can be configured using 61 | 62 | ```gradle 63 | compileJava { 64 | options.compilerArgs += '-AClassModifier=public' 65 | } 66 | ``` 67 | 68 | ## Usage 69 | Decorate your beans with @Codable annotation, if it is specified on base class it is already applied on derived classes, java-codable regards `com.fasterxml.jackson.annotation.JsonProperty` to apply custom name to serialize/deserialize properties and `com.fasterxml.jackson.annotation.JsonFormat` to use specific date formats. 70 | 71 | ### Example 72 | ```java 73 | /** 74 | * 75 | */ 76 | package com.alix.request; 77 | 78 | import io.github.pakistancan.codable.annotation.Codable; 79 | import io.github.pakistancan.codable.annotation.IgnoreProperty; 80 | 81 | /** 82 | * @author muhammadali 83 | * 84 | */ 85 | 86 | @Codable 87 | public class BookInfo { 88 | 89 | private String title; 90 | private String isbn; 91 | private AuthorInfo[] authors; 92 | 93 | @IgnoreProperty 94 | private String ignoredField; 95 | 96 | public String getTitle() { 97 | return title; 98 | } 99 | 100 | public void setTitle(String title) { 101 | this.title = title; 102 | } 103 | 104 | public String getIsbn() { 105 | return isbn; 106 | } 107 | 108 | public void setIsbn(String isbn) { 109 | this.isbn = isbn; 110 | } 111 | 112 | public AuthorInfo[] getAuthors() { 113 | return authors; 114 | } 115 | 116 | public void setAuthors(AuthorInfo[] authors) { 117 | this.authors = authors; 118 | } 119 | 120 | } 121 | ``` 122 | and 123 | ```java 124 | package com.alix.request; 125 | 126 | public class AuthorInfo { 127 | 128 | private String author; 129 | 130 | public String getAuthor() { 131 | return author; 132 | } 133 | 134 | public void setAuthor(String author) { 135 | this.author = author; 136 | } 137 | 138 | } 139 | ``` 140 | 141 | It'll generate swift files for both value objects. 142 | 143 | Output files for the above classes looks like 144 | `BookInfo.swift` 145 | 146 | ```swift 147 | import Foundation 148 | 149 | public class BookInfo: Codable { 150 | public init() { 151 | } 152 | public var title: String = "" 153 | public var isbn: String = "" 154 | public var authors: [AuthorInfo] = [] 155 | private enum CodingKeys: String, CodingKey { 156 | case isbn = "isbn" 157 | case title = "title" 158 | case authors = "authors" 159 | 160 | } 161 | public required init(from decoder: Decoder) throws { 162 | let container = try decoder.container(keyedBy: CodingKeys.self) 163 | 164 | if let title = try container.decodeIfPresent(String.self, forKey: .title) { 165 | self.title = title 166 | } 167 | 168 | if let isbn = try container.decodeIfPresent(String.self, forKey: .isbn) { 169 | self.isbn = isbn 170 | } 171 | 172 | if let authors = try container.decodeIfPresent([AuthorInfo].self, forKey: .authors) { 173 | self.authors = authors 174 | } 175 | } 176 | 177 | public func encode(to encoder: Encoder) throws { 178 | var container = encoder.container(keyedBy: CodingKeys.self) 179 | try container.encodeIfPresent(self.title, forKey: .title) 180 | try container.encodeIfPresent(self.isbn, forKey: .isbn) 181 | try container.encodeIfPresent(self.authors, forKey: .authors) 182 | } 183 | 184 | } 185 | ``` 186 | 187 | `AuthorInfo.swift` 188 | ```swift 189 | import Foundation 190 | 191 | public class AuthorInfo: Codable { 192 | public init() { 193 | } 194 | public var author: String = "" 195 | private enum CodingKeys: String, CodingKey { 196 | case author = "author" 197 | 198 | } 199 | public required init(from decoder: Decoder) throws { 200 | let container = try decoder.container(keyedBy: CodingKeys.self) 201 | 202 | if let author = try container.decodeIfPresent(String.self, forKey: .author) { 203 | self.author = author 204 | } 205 | } 206 | 207 | public func encode(to encoder: Encoder) throws { 208 | var container = encoder.container(keyedBy: CodingKeys.self) 209 | try container.encodeIfPresent(self.author, forKey: .author) 210 | } 211 | 212 | } 213 | ``` 214 | 215 | All primitive classes, user defined classes and collection classes are supported(except queues), here is another example you can use to get a better overview 216 | 217 | ```java 218 | package com.alix.request; 219 | 220 | import java.util.ArrayList; 221 | import java.util.HashMap; 222 | import java.util.HashSet; 223 | import java.util.LinkedHashMap; 224 | import java.util.LinkedHashSet; 225 | import java.util.List; 226 | import java.util.Set; 227 | import java.util.TreeMap; 228 | import java.util.TreeSet; 229 | 230 | import com.fasterxml.jackson.annotation.JsonProperty; 231 | 232 | import io.github.pakistancan.codable.annotation.Codable; 233 | 234 | @Codable 235 | public class Sample { 236 | 237 | @JsonProperty("sample_id") 238 | public int id; 239 | 240 | @JsonProperty("sample_list") 241 | public List sampleList; 242 | 243 | @JsonProperty("sample_list1") 244 | public ArrayList sampleList1; 245 | 246 | @JsonProperty("sample_set") 247 | public Set sampleSet; 248 | 249 | @JsonProperty("sample_list_set") 250 | public Set> sampleListSet; 251 | 252 | public HashSet sampleHashSet; 253 | 254 | public TreeSet sampleTreeSet; 255 | 256 | public LinkedHashSet linkedHashSet; 257 | 258 | public HashMap sampleHashMap; 259 | 260 | public TreeMap sampleTreeMap; 261 | 262 | public LinkedHashMap> linkedHashMap; 263 | 264 | public LinkedHashMap> linkedHashMapSet; 265 | 266 | public String[] inputs; 267 | 268 | public String[][] inputsMap; 269 | 270 | } 271 | ``` 272 | Output of above class should be something like this 273 | 274 | `Sample.swift` 275 | ```swift 276 | import Foundation 277 | 278 | public class Sample: Codable { 279 | public init() { 280 | } 281 | public var id: Int = 0 282 | public var sampleList: [String] = [] 283 | public var sampleList1: [String] = [] 284 | public var sampleSet: [String] = [] 285 | public var sampleListSet: [[String]] = [] 286 | public var sampleHashSet: [String] = [] 287 | public var sampleTreeSet: [String] = [] 288 | public var linkedHashSet: [String] = [] 289 | public var sampleHashMap: [String:String] = [:] 290 | public var sampleTreeMap: [String:String] = [:] 291 | public var linkedHashMap: [String:[String]] = [:] 292 | public var linkedHashMapSet: [String:[String]] = [:] 293 | public var inputs: [String] = [] 294 | public var inputsMap: [[String]] = [] 295 | private enum CodingKeys: String, CodingKey { 296 | case linkedHashSet = "linkedHashSet" 297 | case sampleTreeMap = "sampleTreeMap" 298 | case sampleHashSet = "sampleHashSet" 299 | case inputs = "inputs" 300 | case sampleListSet = "sample_list_set" 301 | case sampleHashMap = "sampleHashMap" 302 | case sampleList = "sample_list" 303 | case sampleSet = "sample_set" 304 | case linkedHashMap = "linkedHashMap" 305 | case sampleTreeSet = "sampleTreeSet" 306 | case linkedHashMapSet = "linkedHashMapSet" 307 | case sampleList1 = "sample_list1" 308 | case inputsMap = "inputsMap" 309 | case id = "sample_id" 310 | 311 | } 312 | public required init(from decoder: Decoder) throws { 313 | let container = try decoder.container(keyedBy: CodingKeys.self) 314 | 315 | if let id = try container.decodeIfPresent(Int.self, forKey: .id) { 316 | self.id = id 317 | } 318 | 319 | if let sampleList = try container.decodeIfPresent([String].self, forKey: .sampleList) { 320 | self.sampleList = sampleList 321 | } 322 | 323 | if let sampleList1 = try container.decodeIfPresent([String].self, forKey: .sampleList1) { 324 | self.sampleList1 = sampleList1 325 | } 326 | 327 | if let sampleSet = try container.decodeIfPresent([String].self, forKey: .sampleSet) { 328 | self.sampleSet = sampleSet 329 | } 330 | 331 | if let sampleListSet = try container.decodeIfPresent([[String]].self, forKey: .sampleListSet) { 332 | self.sampleListSet = sampleListSet 333 | } 334 | 335 | if let sampleHashSet = try container.decodeIfPresent([String].self, forKey: .sampleHashSet) { 336 | self.sampleHashSet = sampleHashSet 337 | } 338 | 339 | if let sampleTreeSet = try container.decodeIfPresent([String].self, forKey: .sampleTreeSet) { 340 | self.sampleTreeSet = sampleTreeSet 341 | } 342 | 343 | if let linkedHashSet = try container.decodeIfPresent([String].self, forKey: .linkedHashSet) { 344 | self.linkedHashSet = linkedHashSet 345 | } 346 | 347 | if let sampleHashMap = try container.decodeIfPresent([String:String].self, forKey: .sampleHashMap) { 348 | self.sampleHashMap = sampleHashMap 349 | } 350 | 351 | if let sampleTreeMap = try container.decodeIfPresent([String:String].self, forKey: .sampleTreeMap) { 352 | self.sampleTreeMap = sampleTreeMap 353 | } 354 | 355 | if let linkedHashMap = try container.decodeIfPresent([String:[String]].self, forKey: .linkedHashMap) { 356 | self.linkedHashMap = linkedHashMap 357 | } 358 | 359 | if let linkedHashMapSet = try container.decodeIfPresent([String:[String]].self, forKey: .linkedHashMapSet) { 360 | self.linkedHashMapSet = linkedHashMapSet 361 | } 362 | 363 | if let inputs = try container.decodeIfPresent([String].self, forKey: .inputs) { 364 | self.inputs = inputs 365 | } 366 | 367 | if let inputsMap = try container.decodeIfPresent([[String]].self, forKey: .inputsMap) { 368 | self.inputsMap = inputsMap 369 | } 370 | } 371 | 372 | public func encode(to encoder: Encoder) throws { 373 | var container = encoder.container(keyedBy: CodingKeys.self) 374 | try container.encodeIfPresent(self.id, forKey: .id) 375 | try container.encodeIfPresent(self.sampleList, forKey: .sampleList) 376 | try container.encodeIfPresent(self.sampleList1, forKey: .sampleList1) 377 | try container.encodeIfPresent(self.sampleSet, forKey: .sampleSet) 378 | try container.encodeIfPresent(self.sampleListSet, forKey: .sampleListSet) 379 | try container.encodeIfPresent(self.sampleHashSet, forKey: .sampleHashSet) 380 | try container.encodeIfPresent(self.sampleTreeSet, forKey: .sampleTreeSet) 381 | try container.encodeIfPresent(self.linkedHashSet, forKey: .linkedHashSet) 382 | try container.encodeIfPresent(self.sampleHashMap, forKey: .sampleHashMap) 383 | try container.encodeIfPresent(self.sampleTreeMap, forKey: .sampleTreeMap) 384 | try container.encodeIfPresent(self.linkedHashMap, forKey: .linkedHashMap) 385 | try container.encodeIfPresent(self.linkedHashMapSet, forKey: .linkedHashMapSet) 386 | try container.encodeIfPresent(self.inputs, forKey: .inputs) 387 | try container.encodeIfPresent(self.inputsMap, forKey: .inputsMap) 388 | } 389 | 390 | } 391 | ``` 392 | 393 | ## Contributing 394 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 395 | Please have a look at [CONTRIBUTING](CONTRIBUTING.md) 396 | 397 | ## License 398 | [MIT](https://github.com/pakistancan/java-codable/blob/main/LICENSE) 399 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | 135 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | io.github.pakistancan 4 | codable-converter 5 | 1.0.5 6 | ${project.groupId}:${project.artifactId} 7 | java-codable is a Java beans to swift codable converter, it is java compile plugin, you need to add this your maven configuration to get it working. 8 | https://github.com/pakistancan/java-codable 9 | jar 10 | 11 | 12 | 13 | MIT License 14 | https://github.com/pakistancan/java-codable/blob/main/LICENSE 15 | 16 | 17 | 18 | 19 | 20 | Muhammad Ali 21 | pakistancan@gmail.com 22 | 23 | 24 | 25 | 26 | scm:git:git://github.com/pakistancan/java-codable.git 27 | scm:git:ssh://github.com:pakistancan/java-codable.git 28 | http://github.com/pakistancan/java-codable/tree/main 29 | 30 | 31 | 32 | 33 | 34 | javax.annotation 35 | javax.annotation-api 36 | 1.3.2 37 | 38 | 39 | 40 | org.slf4j 41 | slf4j-api 42 | 2.0.17 43 | 44 | 45 | 46 | 47 | org.slf4j 48 | slf4j-reload4j 49 | 2.0.17 50 | 51 | 52 | 53 | com.fasterxml.jackson.core 54 | jackson-databind 55 | 2.19.0 56 | 57 | 58 | 59 | 60 | 61 | src/main/java 62 | 63 | 64 | src/main/resources 65 | 66 | **/*.java 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-compiler-plugin 75 | 3.14.0 76 | 77 | 1.8 78 | 1.8 79 | -proc:none 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-source-plugin 85 | 3.3.1 86 | 87 | 88 | attach-sources 89 | 90 | jar-no-fork 91 | 92 | 93 | 94 | 95 | 96 | org.apache.maven.plugins 97 | maven-javadoc-plugin 98 | 3.11.2 99 | 100 | 101 | attach-javadocs 102 | 103 | jar 104 | 105 | 106 | 107 | 108 | 109 | org.sonatype.plugins 110 | nexus-staging-maven-plugin 111 | 1.7.0 112 | true 113 | 114 | ossrh 115 | https://s01.oss.sonatype.org/ 116 | true 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-gpg-plugin 122 | 3.2.7 123 | 124 | 125 | sign-artifacts 126 | verify 127 | 128 | sign 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | ossrh 139 | https://s01.oss.sonatype.org/content/repositories/snapshots 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 4 | 5 | Fixes # (issue) 6 | 7 | ## Type of change 8 | 9 | - [ ] Bug fix (non-breaking change which fixes an issue) 10 | - [ ] New feature (non-breaking change which adds functionality) 11 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 12 | - [ ] This change requires a documentation update 13 | 14 | # How Has This Been Tested? 15 | 16 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 17 | 18 | - [ ] Test A 19 | - [ ] Test B 20 | 21 | **Test Configuration**: 22 | * Sample Java files: 23 | * Output Swift file: 24 | 25 | # Checklist: 26 | 27 | - [ ] My code follows the style guidelines of this project 28 | - [ ] I have performed a self-review of my own code 29 | - [ ] I have commented my code, particularly in hard-to-understand areas 30 | - [ ] I have made corresponding changes to the documentation 31 | - [ ] My changes generate no new warnings 32 | - [ ] I have added tests that prove my fix is effective or that my feature works 33 | - [ ] New and existing unit tests pass locally with my changes 34 | - [ ] Any dependent changes have been merged and published in downstream modules 35 | -------------------------------------------------------------------------------- /src/main/java/io/github/pakistancan/codable/annotation/Codable.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.github.pakistancan.codable.annotation; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Inherited; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * @author muhammadali 14 | * 15 | * Generates Codable, also generate reference classes that are 16 | * referenced in the class if they are in the same package 17 | */ 18 | 19 | @Inherited 20 | @Retention(RetentionPolicy.CLASS) 21 | @Target(ElementType.TYPE) 22 | public @interface Codable { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/github/pakistancan/codable/annotation/IgnoreProperty.java: -------------------------------------------------------------------------------- 1 | package io.github.pakistancan.codable.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Inherited; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Inherited 10 | @Retention(RetentionPolicy.CLASS) 11 | @Target(ElementType.FIELD) 12 | /** 13 | * 14 | * @author muhammadali 15 | * 16 | * Ignores property and don't generate a mapping for the given property 17 | * 18 | */ 19 | public @interface IgnoreProperty { 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/io/github/pakistancan/codable/model/ObjectType.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.github.pakistancan.codable.model; 5 | 6 | /** 7 | * @author muhammadali 8 | * 9 | */ 10 | 11 | /** 12 | * 13 | * This enum specifies whether type is sample, date or enum 14 | * 15 | */ 16 | public enum ObjectType { 17 | SIMPLE, DATE, ENUM 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/github/pakistancan/codable/model/TypeInfo.java: -------------------------------------------------------------------------------- 1 | package io.github.pakistancan.codable.model; 2 | 3 | import java.util.Date; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * TypeInfo represent type info mapping from Java primitive types to Swift 9 | * primitive types 10 | */ 11 | public class TypeInfo { 12 | 13 | private static final Map mapping = new HashMap<>(); 14 | 15 | static { 16 | mapping.put("byte", new TypeInfo("Int8", "0")); 17 | mapping.put(Byte.class.getCanonicalName(), new TypeInfo("Int8", "0")); 18 | mapping.put(String.class.getCanonicalName(), new TypeInfo("String", "\"\"")); 19 | mapping.put("int", new TypeInfo("Int", "0")); 20 | mapping.put(Integer.class.getCanonicalName(), new TypeInfo("Int", "0")); 21 | mapping.put(Number.class.getCanonicalName(), new TypeInfo("Int", "0")); 22 | mapping.put("short", new TypeInfo("Int16", "0")); 23 | mapping.put(Short.class.getCanonicalName(), new TypeInfo("Int16", "0")); 24 | mapping.put("float", new TypeInfo("Float", "0.0")); 25 | mapping.put(Float.class.getCanonicalName(), new TypeInfo("Float", "0.0")); 26 | mapping.put("double", new TypeInfo("Double", "0.0")); 27 | mapping.put(Double.class.getCanonicalName(), new TypeInfo("Double", "0.0")); 28 | mapping.put("boolean", new TypeInfo("Bool", "false")); 29 | mapping.put(Boolean.class.getCanonicalName(), new TypeInfo("Bool", "false")); 30 | mapping.put(Date.class.getCanonicalName(), 31 | new TypeInfo("Date", "Date(timeIntervalSince1970: 0)", ObjectType.DATE)); 32 | mapping.put("long", new TypeInfo("Int64", "0")); 33 | mapping.put(Long.class.getCanonicalName(), new TypeInfo("Int64", "0")); 34 | mapping.put(Object.class.getCanonicalName(), new TypeInfo("Any", null)); 35 | mapping.put("?", new TypeInfo("Any", null)); 36 | mapping.put("char", new TypeInfo("Int16", null)); 37 | mapping.put(Character.class.getCanonicalName(), new TypeInfo("Int16", null)); 38 | 39 | } 40 | 41 | public String defaultValue; 42 | public String swiftName; 43 | public ObjectType type; 44 | 45 | /** 46 | * @param defaultValue Default value of swift type 47 | * @param swiftName Name of swift type 48 | */ 49 | public TypeInfo(String swiftName, String defaultValue) { 50 | this(swiftName, defaultValue, ObjectType.SIMPLE); 51 | } 52 | 53 | /** 54 | * @param swiftName Name of swift type 55 | * @param defaultValue Default value of swift type 56 | * @param type type of object {@link ObjectType} 57 | */ 58 | public TypeInfo(String swiftName, String defaultValue, ObjectType type) { 59 | super(); 60 | this.defaultValue = defaultValue; 61 | this.swiftName = swiftName; 62 | this.type = type; 63 | } 64 | 65 | /** 66 | * Returns type info if exists, otherwise null. 67 | * 68 | * @param name name of type trying to lookup 69 | * @return returns {@link TypeInfo} or null 70 | */ 71 | public static TypeInfo getTypeInfo(String name) { 72 | if (mapping.containsKey(name)) { 73 | return mapping.get(name); 74 | } 75 | return null; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/io/github/pakistancan/codable/processor/CodableGenerator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package io.github.pakistancan.codable.processor; 5 | 6 | import com.fasterxml.jackson.annotation.JsonFormat; 7 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 8 | import com.fasterxml.jackson.annotation.JsonProperty; 9 | import io.github.pakistancan.codable.annotation.Codable; 10 | import io.github.pakistancan.codable.annotation.IgnoreProperty; 11 | import io.github.pakistancan.codable.model.ObjectType; 12 | import io.github.pakistancan.codable.model.TypeInfo; 13 | 14 | import org.apache.log4j.BasicConfigurator; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | 19 | import javax.annotation.processing.*; 20 | import javax.lang.model.SourceVersion; 21 | import javax.lang.model.element.*; 22 | import javax.lang.model.type.TypeMirror; 23 | import javax.lang.model.util.ElementFilter; 24 | import javax.tools.Diagnostic.Kind; 25 | import java.io.File; 26 | import java.io.FileOutputStream; 27 | import java.io.IOException; 28 | import java.util.*; 29 | import java.util.concurrent.ConcurrentHashMap; 30 | import java.util.concurrent.ConcurrentMap; 31 | 32 | import static java.nio.charset.StandardCharsets.UTF_8; 33 | 34 | /** 35 | * @author muhammadali 36 | */ 37 | @SupportedAnnotationTypes("io.github.pakistancan.codable.annotation.Codable") 38 | @SupportedSourceVersion(SourceVersion.RELEASE_6) 39 | @SupportedOptions(value = {"OutputDir", "PackagePrefix", "ClassModifier", "GenerateStructs"}) 40 | public class CodableGenerator extends AbstractProcessor { 41 | 42 | private static final String EXT = ".swift"; 43 | private static final Set reserveWords = new HashSet<>(); 44 | private static final Set restrictedWords = new HashSet<>(); 45 | private static final String[] collectionsInterfaces = new String[]{Set.class.getCanonicalName(), 46 | List.class.getCanonicalName(), Map.class.getCanonicalName()}; 47 | private static final Set allowedModifiers = new HashSet<>(); 48 | 49 | static { 50 | 51 | reserveWords.add("operator"); 52 | reserveWords.add("func"); 53 | restrictedWords.add("self"); 54 | BasicConfigurator.configure(); 55 | } 56 | 57 | static { 58 | allowedModifiers.add("public"); 59 | allowedModifiers.add("open"); 60 | } 61 | 62 | private final Logger logger = LoggerFactory.getLogger(CodableGenerator.class); 63 | private final ConcurrentMap collectionTypeMirror = new ConcurrentHashMap<>(); 64 | private final ConcurrentMap generatedEnums = new ConcurrentHashMap<>(); 65 | private String outputDir = "./generated/"; 66 | private String packagePrefix = "com.alix."; 67 | private String classModifier = "public"; 68 | 69 | private boolean generateStructs = false; 70 | private boolean generateFormatters = false; 71 | 72 | /** 73 | * 74 | */ 75 | public CodableGenerator() { 76 | } 77 | 78 | @Override 79 | public synchronized void init(ProcessingEnvironment processingEnv) { 80 | super.init(processingEnv); 81 | 82 | Map options = this.processingEnv.getOptions(); 83 | this.logger.debug("Options :: " + options); 84 | String outDir = options.get("OutputDir"); 85 | if (outDir != null) { 86 | this.outputDir = outDir; 87 | } 88 | if (!this.outputDir.endsWith("/")) { 89 | this.outputDir += "/"; 90 | } 91 | if (!this.outputDir.endsWith("swift/")) { 92 | this.outputDir += "swift/"; 93 | } 94 | String prefix = options.get("PackagePrefix"); 95 | if (prefix != null) { 96 | this.packagePrefix = prefix; 97 | } 98 | String modifier = options.get("ClassModifier"); 99 | if (modifier != null && allowedModifiers.contains(modifier)) { 100 | this.classModifier = modifier; 101 | } 102 | 103 | String generateStruct = options.get("GenerateStructs"); 104 | if (generateStruct != null && generateStruct.equals("true")) { 105 | this.generateStructs = true; 106 | this.classModifier = "public"; 107 | } 108 | 109 | for (String ifaceName : collectionsInterfaces) { 110 | this.logger.debug("Interface Name" + ifaceName); 111 | collectionTypeMirror.put(ifaceName, 112 | this.processingEnv.getElementUtils().getTypeElement(ifaceName).asType()); 113 | } 114 | 115 | } 116 | 117 | // It'll be better if we use templating to generate swift classes instead of 118 | // string concatenation 119 | private void generateCodable(TypeElement element, RoundEnvironment roundEnv) { 120 | 121 | TypeMirror parent = element.getSuperclass(); 122 | this.logger.debug("parent :: " + parent); 123 | String parentClass = ""; 124 | if (!Object.class.getCanonicalName().equals(parent.toString())) { 125 | parentClass = parent.toString(); 126 | if (parentClass.contains(".")) { 127 | parentClass = parentClass.substring(parentClass.lastIndexOf(".") + 1); 128 | } 129 | } 130 | 131 | StringBuilder builder = new StringBuilder(); 132 | this.logger.debug("Encloding Elems :: " + element.getEnclosedElements()); 133 | 134 | builder.append("import Foundation\n\n"); 135 | String swiftTypeName = " class "; 136 | logger.debug("this.generateStructs: " + this.generateStructs + " parentClass.length(): " + parentClass.length()); 137 | 138 | if (this.generateStructs && parentClass.length() == 0) { 139 | swiftTypeName = " struct "; 140 | } 141 | builder.append(classModifier).append(swiftTypeName).append(element.getSimpleName()); 142 | boolean override = false; 143 | 144 | if (parentClass.length() > 0) { 145 | builder.append(": ").append(parentClass).append(" {\n"); 146 | builder.append(" public override init() {\n super.init()\n }\n"); 147 | override = true; 148 | } else { 149 | builder.append(": Codable {\n"); 150 | if (!this.generateStructs) { 151 | builder.append(" public init() {\n }\n"); 152 | } 153 | } 154 | 155 | List tElemns = processingEnv.getElementUtils().getAllMembers(element); 156 | tElemns = ElementFilter.fieldsIn(tElemns); 157 | Map> enumMap = new HashMap<>(); 158 | Map jsonMap = new HashMap<>(); 159 | 160 | StringBuilder encoder = new StringBuilder(); 161 | encoder.append("\n public "); 162 | if (override) { 163 | encoder.append("override "); 164 | } 165 | encoder.append("func encode(to encoder: Encoder) throws {\n"); 166 | 167 | StringBuilder decoder = new StringBuilder(); 168 | if (this.generateStructs) { 169 | decoder.append("\n public init(from decoder: Decoder) throws {\n"); 170 | } else { 171 | decoder.append("\n public required init(from decoder: Decoder) throws {\n"); 172 | } 173 | if (parentClass.length() > 0) { 174 | decoder.append(" try super.init(from: decoder)\n"); 175 | encoder.append(" try super.encode(to: encoder)\n"); 176 | 177 | } 178 | boolean addedContainer = false; 179 | 180 | for (Element field : tElemns) { 181 | VariableElement var = (VariableElement) field; 182 | logger.debug("Var :: " + var); 183 | if (var.getModifiers().contains(Modifier.STATIC)) { 184 | this.logger.debug("continuing "); 185 | continue; 186 | } 187 | JsonIgnoreProperties jsonIgnoreProp = var.getAnnotation(JsonIgnoreProperties.class); 188 | IgnoreProperty ignoreProp = var.getAnnotation(IgnoreProperty.class); 189 | if (ignoreProp != null || jsonIgnoreProp != null) { 190 | this.logger.debug("ignore property annotation found"); 191 | continue; 192 | } 193 | 194 | if (!var.getEnclosingElement().getSimpleName().equals(element.getSimpleName())) { 195 | this.logger.debug(" super class continuing "); 196 | continue; 197 | } 198 | 199 | this.logger.debug("Field :: " + var.getSimpleName() + " " + field.asType().toString() + " :: " 200 | + field.asType().getClass().getCanonicalName()); 201 | String type = field.asType().toString(); 202 | 203 | TypeInfo newType = getTypeInfo(type, field, enumMap, roundEnv); 204 | 205 | if (newType == null) { 206 | newType = getTypeInfo(type); 207 | } 208 | String varName = field.getSimpleName().toString(); 209 | if (Character.isUpperCase(varName.charAt(0))) { 210 | varName = Character.toLowerCase(varName.charAt(0)) + varName.substring(1); 211 | } 212 | if (restrictedWords.contains(varName)) { 213 | throw new RuntimeException("variable name not allowed" + varName); 214 | } 215 | if (reserveWords.contains(varName)) { 216 | varName = "`" + varName + "`"; 217 | } 218 | JsonProperty prop = var.getAnnotation(JsonProperty.class); 219 | if (null != prop) { 220 | jsonMap.put(varName, prop.value()); 221 | this.logger.debug("Json Map :: " + prop); 222 | } else { 223 | jsonMap.put(varName, varName); 224 | } 225 | 226 | if (!addedContainer) { 227 | decoder.append(" let container = try decoder.container(keyedBy: CodingKeys.self)\n"); 228 | encoder.append(" var container = encoder.container(keyedBy: CodingKeys.self)\n"); 229 | addedContainer = true; 230 | } 231 | 232 | builder.append(" public var ").append(varName).append(": ").append(newType.swiftName); 233 | String typeToMarshal = newType.swiftName; 234 | if (newType.defaultValue != null) { 235 | builder.append(" = ").append(newType.defaultValue); 236 | } else { 237 | builder.append("?"); 238 | typeToMarshal = typeToMarshal + "?"; 239 | } 240 | 241 | String assignedValue = varName; 242 | String encodedValueName = "self." + varName; 243 | 244 | if (newType.type == ObjectType.ENUM) { 245 | assignedValue = newType.swiftName + "(rawValue: " + varName + ")!"; 246 | encodedValueName = "self." + varName + ".rawValue"; 247 | typeToMarshal = "String"; 248 | } else if (newType.type == ObjectType.DATE) { 249 | generateFormatters = true; 250 | JsonFormat dateFormat = var.getAnnotation(JsonFormat.class); 251 | String pattern = "yyyy-MM-dd'T'HH:mm:ssZ"; 252 | 253 | if (dateFormat != null) { 254 | pattern = dateFormat.pattern(); 255 | } 256 | 257 | if (!"".equals(pattern)) { 258 | typeToMarshal = "String"; 259 | 260 | assignedValue = "Formatters.shared.getDate(format: \"" + pattern + "\", date: " + varName + ")"; 261 | encodedValueName = "Formatters.shared.formatDate(format: \"" + pattern + "\", date: " + "self." 262 | + varName + ")"; 263 | } 264 | 265 | } 266 | 267 | builder.append("\n"); 268 | 269 | decoder.append("\n if let ").append(varName).append(" = try container.decodeIfPresent("). 270 | append(typeToMarshal).append(".self, forKey: .").append(varName).append(") {\n"); 271 | 272 | decoder.append(" self.").append(varName).append(" = ").append(assignedValue); 273 | decoder.append("\n }\n"); 274 | 275 | encoder.append( 276 | " try container.encodeIfPresent(").append(encodedValueName). 277 | append(", forKey: .").append(varName).append(")\n"); 278 | 279 | } 280 | decoder.append(" }\n"); 281 | encoder.append(" }\n"); 282 | 283 | if (jsonMap.size() > 0) { 284 | builder.append(" private enum CodingKeys: String, CodingKey {\n"); 285 | for (Map.Entry elem : jsonMap.entrySet()) { 286 | builder.append(" case ").append(elem.getKey()).append(" = \"").append(elem.getValue()).append("\"\n"); 287 | } 288 | builder.append("\n }"); 289 | } 290 | builder.append(decoder); 291 | builder.append(encoder); 292 | builder.append("\n}"); 293 | 294 | String prefix = "RES/"; 295 | if (element.getQualifiedName().toString().contains(".request.")) { 296 | prefix = "REQ/"; 297 | } 298 | 299 | this.writeOutput(builder.toString(), outputDir + prefix, element.getSimpleName() + EXT); 300 | } 301 | 302 | @Override 303 | public synchronized boolean process(Set annotations, RoundEnvironment roundEnv) { 304 | for (Element elem : roundEnv.getElementsAnnotatedWith(Codable.class)) { 305 | Codable complexity = elem.getAnnotation(Codable.class); 306 | String message = "annotation found in " + elem.getSimpleName() + " with complexity " + complexity; 307 | 308 | TypeElement element = (TypeElement) elem; 309 | generateCodable(element, roundEnv); 310 | processingEnv.getMessager().printMessage(Kind.NOTE, message); 311 | } 312 | 313 | if (generatedEnums.size() > 0) { 314 | String prefix = "Enums/"; 315 | for (Map.Entry elem : generatedEnums.entrySet()) { 316 | this.writeOutput(elem.getValue().toString(), outputDir + prefix, elem.getKey() + EXT); 317 | } 318 | 319 | } 320 | 321 | if (this.generateFormatters) { 322 | this.generateFormatter(); 323 | } 324 | 325 | return false; 326 | } 327 | 328 | private synchronized TypeInfo getTypeInfo(String type, Element field, Map> enumMap, 329 | RoundEnvironment roundEnv) { 330 | TypeInfo newType = null; 331 | 332 | this.logger.debug("PARSING: " + type); 333 | 334 | if (type.endsWith("[]")) { 335 | String tp = type.substring(0, type.lastIndexOf("[]")); 336 | TypeInfo typeInfo = getTypeInfo(tp, field.getEnclosingElement(), enumMap, roundEnv); 337 | if (typeInfo != null) { 338 | newType = new TypeInfo("[" + typeInfo.swiftName + "]", "[]"); 339 | } else { 340 | newType = new TypeInfo("[Any]", "[]"); 341 | } 342 | return newType; 343 | } 344 | 345 | if (type.startsWith(Map.class.getCanonicalName())) { 346 | String tp = type.substring(type.indexOf("<") + 1); 347 | tp = tp.substring(0, tp.lastIndexOf(">")); 348 | 349 | String first = tp.substring(0, tp.lastIndexOf(",")).trim(); 350 | tp = tp.substring(tp.lastIndexOf(",") + 1); 351 | tp = tp.trim(); 352 | TypeInfo firstType = getTypeInfo(first, field.getEnclosingElement(), enumMap, roundEnv); 353 | TypeInfo secondType = getTypeInfo(tp, field.getEnclosingElement(), enumMap, roundEnv); 354 | 355 | if (firstType != null && secondType != null) { 356 | newType = new TypeInfo("[" + firstType.swiftName + ":" + secondType.swiftName + "]", "[:]"); 357 | } else { 358 | newType = new TypeInfo("[AnyHash: Any]", "[:]"); 359 | } 360 | return newType; 361 | } 362 | 363 | boolean isSet = type.startsWith(Set.class.getCanonicalName()); 364 | boolean isList = type.startsWith(List.class.getCanonicalName()); 365 | 366 | if (isSet || isList) { 367 | String tp = type.substring(type.indexOf("<") + 1); 368 | tp = tp.substring(0, tp.lastIndexOf(">")); 369 | 370 | TypeInfo typeInfo = getTypeInfo(tp, field.getEnclosingElement(), enumMap, roundEnv); 371 | if (typeInfo != null) { 372 | newType = new TypeInfo("[" + typeInfo.swiftName + "]", "[]"); 373 | } else { 374 | newType = new TypeInfo("[Any]", "[]"); 375 | } 376 | return newType; 377 | } 378 | 379 | if (type.startsWith(packagePrefix)) { 380 | 381 | TypeElement newTypeElem = processingEnv.getElementUtils().getTypeElement(field.asType().toString()); 382 | System.err.println("Field::" + field.asType() + " TypeElement:: " + newTypeElem); 383 | 384 | System.err.println("newType :: " + newTypeElem.getKind()); 385 | if (newTypeElem.getKind() != ElementKind.ENUM) { 386 | newTypeElem = processingEnv.getElementUtils().getTypeElement(type); 387 | generateCodable(newTypeElem, roundEnv); 388 | 389 | type = type.substring(type.lastIndexOf(".") + 1); 390 | newType = new TypeInfo(type, null); 391 | } else { 392 | List elems = newTypeElem.getEnclosedElements(); 393 | 394 | boolean isGenerated = generatedEnums.containsKey(newTypeElem.getSimpleName().toString()); 395 | 396 | StringBuilder sb = new StringBuilder(); 397 | if (!isGenerated) { 398 | sb.append("\n").append(classModifier).append(" enum ").append(newTypeElem.getSimpleName()).append(": String, Codable {\n"); 399 | } 400 | List enumElems = new ArrayList<>(); 401 | for (Element elm : elems) { 402 | if (elm.getKind() != ElementKind.ENUM_CONSTANT) { 403 | continue; 404 | } 405 | if (!isGenerated) { 406 | sb.append(" case ").append(elm.getSimpleName()).append(" = \"").append(elm.getSimpleName()).append("\"\n"); 407 | } 408 | enumElems.add(elm.getSimpleName().toString()); 409 | } 410 | if (!isGenerated) { 411 | sb.append("\n}\n"); 412 | generatedEnums.put(newTypeElem.getSimpleName().toString(), sb); 413 | 414 | } 415 | 416 | enumMap.put(field.getSimpleName().toString(), enumElems); 417 | type = type.substring(type.lastIndexOf(".") + 1); 418 | if (enumElems.size() > 0) { 419 | newType = new TypeInfo(type, "." + enumElems.get(0), ObjectType.ENUM); 420 | } else { 421 | newType = new TypeInfo(type, null, ObjectType.ENUM); 422 | } 423 | 424 | } 425 | } else { 426 | // Handle collection child here 427 | if (type.contains("<")) { 428 | 429 | String tp = type.substring(0, type.indexOf("<")); 430 | this.logger.debug("type: " + tp); 431 | TypeElement tm = processingEnv.getElementUtils().getTypeElement(tp); 432 | 433 | // TypeElement tm = mirror; 434 | do { 435 | this.logger.debug("class:: " + tm); 436 | for (TypeMirror iface : tm.getInterfaces()) { 437 | for (Map.Entry infaceName : this.collectionTypeMirror.entrySet()) { 438 | TypeMirror ifaceMirror = infaceName.getValue(); 439 | this.logger.debug("iface:: " + iface + " ifaceMirror:: " + ifaceMirror); 440 | this.logger.debug("Assignable01:: " 441 | + processingEnv.getTypeUtils().isAssignable(tm.asType(), ifaceMirror)); 442 | this.logger.debug("Assignable02:: " 443 | + processingEnv.getTypeUtils().isAssignable(ifaceMirror, tm.asType())); 444 | 445 | this.logger.debug( 446 | "Assignable1:: " + processingEnv.getTypeUtils().isAssignable(iface, ifaceMirror)); 447 | this.logger.debug( 448 | "Assignable2:: " + processingEnv.getTypeUtils().isAssignable(ifaceMirror, iface)); 449 | if (processingEnv.getTypeUtils().isAssignable(ifaceMirror, iface) 450 | || iface.toString().contains(ifaceMirror.toString())) { 451 | String newTypeName = type.replace(tp, infaceName.getKey()); 452 | return getTypeInfo(newTypeName, field, enumMap, roundEnv); 453 | 454 | } 455 | } 456 | 457 | } 458 | 459 | String superClassName = tm.getSuperclass().toString(); 460 | this.logger.debug("superClassName:: " + superClassName); 461 | if (superClassName.equals(Object.class.getCanonicalName())) { 462 | break; 463 | } 464 | if (superClassName.contains("<")) { 465 | superClassName = superClassName.substring(0, superClassName.indexOf("<")); 466 | } 467 | 468 | this.logger.debug("superClassName:: " + superClassName); 469 | tm = processingEnv.getElementUtils().getTypeElement(superClassName); 470 | this.logger.debug("superClassName:: " + tm + "ifaces" + tm.getInterfaces()); 471 | 472 | } while (tm != null); 473 | 474 | } 475 | 476 | } 477 | 478 | if (null == newType) { 479 | return getTypeInfo(type); 480 | } 481 | return newType; 482 | } 483 | 484 | private TypeInfo getTypeInfo(String tp) { 485 | TypeInfo info = TypeInfo.getTypeInfo(tp); 486 | if (info != null) { 487 | return info; 488 | } 489 | 490 | if (tp.startsWith(packagePrefix)) { 491 | tp = tp.substring(tp.lastIndexOf(".") + 1); 492 | return new TypeInfo(tp, null); 493 | } 494 | throw new RuntimeException("Unknown type " + tp); 495 | } 496 | 497 | private void generateFormatter() { 498 | 499 | String prefix = "Formatter/"; 500 | String key = "Formatters"; 501 | 502 | String formatterClass = "import Foundation\n" + "\n" + classModifier + " class Formatters {\n" 503 | + " private var formatters: [String: DateFormatter] = [:]\n" 504 | + " public static let shared: Formatters = Formatters()\n" + "\n" 505 | + " public func getFormatter(format: String) -> DateFormatter {\n" 506 | + " if let formatter = formatters[format] {\n" + " return formatter\n" 507 | + " } else {\n" + " let formatter = DateFormatter()\n" 508 | + " formatter.dateFormat = format\n" + " formatters[format] = formatter\n" 509 | + " return formatter\n" + " }\n" + " }\n" + " \n" 510 | + " public func formatDate(format: String, date: Date) -> String {\n" 511 | + " return self.getFormatter(format: format).string(from: date)\n" + " }\n" + " \n" 512 | + " public func getDate(format: String, date: String) -> Date {\n" 513 | + " return self.getFormatter(format: format).date(from: date) ?? Date(timeIntervalSince1970: 0)\n" 514 | + " }\n" + " \n" + " public func getDate(interval: Int64) -> Date {\n" 515 | + " return Date(timeIntervalSince1970: (Double(interval)/1000.0))\n" + " }\n" 516 | + " public func getMilliSeconds(date: Date) -> Int64 {\n" 517 | + " return Int64(date.timeIntervalSince1970 * 1000)\n" + " }\n" + "}\n"; 518 | 519 | this.writeOutput(formatterClass, outputDir + prefix, key + EXT); 520 | 521 | } 522 | 523 | private void writeOutput(String output, String directory, String filename) { 524 | File file = new File(directory); 525 | if (!file.exists()) { 526 | file.mkdirs(); 527 | } 528 | 529 | String newPath = directory + filename; 530 | if (directory.endsWith(File.separator)) { 531 | newPath = directory + File.separator + filename; 532 | } 533 | 534 | try (FileOutputStream fout = new FileOutputStream(newPath)) { 535 | fout.write(output.getBytes(UTF_8)); 536 | } catch (IOException e) { 537 | logger.error(e.getMessage(), e); 538 | } 539 | 540 | } 541 | 542 | } 543 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | io.github.pakistancan.codable.processor.CodableGenerator 2 | --------------------------------------------------------------------------------