├── .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 | [](https://github.com/pakistancan/java-codable/actions/workflows/maven.yml) [](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 extends Element> 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 extends TypeElement> 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 extends Element> 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 |
--------------------------------------------------------------------------------