Skip to content

Commit a662629

Browse files
committed
update readme
1 parent 8d3d533 commit a662629

File tree

1 file changed

+142
-44
lines changed

1 file changed

+142
-44
lines changed

README.md

Lines changed: 142 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,160 @@
1-
## Data-compat
1+
## Kotlin-data-compat
22

33
### Context
4-
The Data-compat library resolves the Java binary incompatibility that library developers face when using Kotlin data classes. In a nutshell, every change to a data class results in major breaking change. This project attempts to resolve this binary incompatibility by using annotation processing. Users can keep using their original data classes, but the generated code will compatible for Java consumption.
5-
4+
The Data-compat library resolves the Java binary incompatibility that library developers face when using Kotlin data classes. In a nutshell, every change to a data class results in major breaking change. This project attempts to resolve this binary incompatibility by using annotation processing. Users can keep using their original data classes definitions, but the generated code will compatible for Java consumption.
65
To read more about this incompatibility, please refer to Jake Wharton's [Public API challenges in Kotlin](https://jakewharton.com/public-api-challenges-in-kotlin/) blogpost.
76

8-
### How
9-
Data-compat uses [Kotlin Symbol Processing API (KSP)](https://kotlinlang.org/docs/ksp-overview.html) in combination with [KotlinPoet](https://square.github.io/kotlinpoet/) to generate Kotlin classes that are Java binary compatible. Input for the system is a private data class that supports a `@DataCompat` annotation, library developers can easily convert their existing data classes but make them private and add the required annotation. The code generator will output a Kotlin class that supports a builder pattern compatible for Java usage.
7+
This library uses [Kotlin Symbol Processing API (KSP)](https://kotlinlang.org/docs/ksp-overview.html) in combination with [KotlinPoet](https://square.github.io/kotlinpoet/) to generate Kotlin classes. Input is a private data class that supports a `@DataCompat` annotation and the code generator outputs a Kotlin class that supports a builder pattern compatible for Java usage.
8+
9+
### Add to your project
10+
The project is hosted on [jitpack](https://jitpack.io/) and requires to add jitpack.io lookup to your gradle configuration:
11+
12+
```groovy
13+
allprojects {
14+
repositories {
15+
maven { url 'https://jitpack.io' }
16+
}
17+
}
18+
```
19+
20+
Since this project uses ksp, you will have to include it:
21+
```groovy
22+
plugins {
23+
id 'com.google.devtools.ksp' version '1.6.10-1.0.2' apply false
24+
}
25+
```
26+
27+
And you will have to include the required dependencies:
28+
29+
```groovy
30+
dependencies {
31+
implementation 'com.github.tobrun.kotlin-data-compat:annotation:0.1.0'
32+
ksp 'com.github.tobrun.kotlin-data-compat:processor:0.1.0'
33+
}
34+
```
35+
1036

11-
#### Input
37+
### Getting started
38+
39+
Given an exisiting data class:
40+
- add `@DataCompat` annotation
41+
- mark class private
42+
- Append `Data` to the class name
43+
44+
For example:
1245

1346
```kotlin
47+
/**
48+
* Represents a person.
49+
* @property name The full name.
50+
* @property nickname The nickname.
51+
* @property age The age.
52+
*/
1453
@DataCompat
1554
private data class PersonData(val name: String, val nickname: String? = null, val age: Int)
1655
```
1756

18-
#### Output
57+
After compilation, the following class will be generated:
1958

2059
```kotlin
21-
class Person private constructor(
22-
val name: String,
23-
val nickname: String?,
24-
val age: Int
60+
/**
61+
* Represents a person.
62+
* @property name The full name.
63+
* @property nickname The nickname.
64+
* @property age The age.
65+
*/
66+
public class Person private constructor(
67+
public val name: String,
68+
public val nickname: String?,
69+
public val age: Int
2570
) {
26-
override fun toString() = "Person(name=$name, nickname=$nickname, age=$age)"
27-
override fun equals(other: Any?) = other is Person
28-
&& name == other.name
29-
&& nickname == other.nickname
30-
&& age == other.age
31-
override fun hashCode() = Objects.hash(name, nickname, age)
32-
33-
class Builder {
34-
@set:JvmSynthetic // Hide 'void' setter from Java
35-
var name: String? = null
36-
@set:JvmSynthetic // Hide 'void' setter from Java
37-
var nickname: String? = null
38-
@set:JvmSynthetic // Hide 'void' setter from Java
39-
var age: Int = 0
40-
41-
fun setName(name: String?) = apply { this.name = name }
42-
fun setNickname(nickname: String?) = apply { this.nickname = nickname }
43-
fun setAge(age: Int) = apply { this.age = age }
44-
45-
fun build() = Person(name!!, nickname, age)
71+
public override fun toString() = "Person(name=$name, nickname=$nickname, age=$age)"
72+
73+
public override fun equals(other: Any?): Boolean = other is Person
74+
&& name == other.name
75+
&& nickname == other.nickname
76+
&& age == other.age
77+
78+
public override fun hashCode(): Int = Objects.hash(name, nickname, age)
79+
80+
/**
81+
* Composes and builds a [Person] object.
82+
*
83+
* This is a concrete implementation of the builder design pattern.
84+
*
85+
* @property name The full name.
86+
* @property nickname The nickname.
87+
* @property age The age.
88+
*/
89+
public class Builder {
90+
@set:JvmSynthetic
91+
public var name: String? = null
92+
93+
@set:JvmSynthetic
94+
public var nickname: String? = null
95+
96+
@set:JvmSynthetic
97+
public var age: Int? = null
98+
99+
/**
100+
* Set the full name.
101+
*
102+
* @param name the full name.
103+
* @return Builder
104+
*/
105+
public fun setName(name: String?): Builder {
106+
this.name = name
107+
return this
108+
}
109+
110+
/**
111+
* Set the nickname.
112+
*
113+
* @param nickname the nickname.
114+
* @return Builder
115+
*/
116+
public fun setNickname(nickname: String?): Builder {
117+
this.nickname = nickname
118+
return this
119+
}
120+
121+
/**
122+
* Set the age.
123+
*
124+
* @param age the age.
125+
* @return Builder
126+
*/
127+
public fun setAge(age: Int?): Builder {
128+
this.age = age
129+
return this
130+
}
131+
132+
/**
133+
* Returns a [Person] reference to the object being constructed by the builder.
134+
*
135+
* Throws an [IllegalArgumentException] when a non-null property wasn't initialised.
136+
*
137+
* @return Person
138+
*/
139+
public fun build(): Person {
140+
if (name==null) {
141+
throw IllegalArgumentException("Null name found when building Person.")
142+
}
143+
if (age==null) {
144+
throw IllegalArgumentException("Null age found when building Person.")
145+
}
146+
return Person(name!!, nickname, age!!)
147+
}
46148
}
47149
}
48150

49-
@JvmSynthetic // Hide from Java callers who should use Builder.
50-
fun Person(initializer: Person.Builder.() -> Unit): Person {
51-
return Person.Builder().apply(initializer).build()
52-
}
151+
/**
152+
* Creates a [Person] through a DSL-style builder.
153+
*
154+
* @param initializer the intialisation block
155+
* @return Person
156+
*/
157+
@JvmSynthetic
158+
public fun Person(initializer: Person.Builder.() -> Unit): Person =
159+
Person.Builder().apply(initializer).build()
53160
```
54-
55-
### State of project
56-
57-
This project is under development, the following still needs to be done:
58-
- [ ] integrate output compatible with above using Kotlin Poet
59-
- [ ] split up annotation from processor module?
60-
- [ ] integrate example shown above
61-
- [ ] add unit tests
62-
- [ ] publish to maven

0 commit comments

Comments
 (0)