Skip to content

Commit bb7d1dc

Browse files
authored
Merge pull request #31 from tobrun/kdz-new-functionality
Additional functionality
2 parents f1f700b + 8b2f1c5 commit bb7d1dc

File tree

7 files changed

+172
-80
lines changed

7 files changed

+172
-80
lines changed

README.md

Lines changed: 97 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ And you will have to include the required dependencies:
2828

2929
```groovy
3030
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'
31+
implementation 'com.github.tobrun.kotlin-data-compat:annotation:0.3.0'
32+
ksp 'com.github.tobrun.kotlin-data-compat:processor:0.3.0'
3333
}
3434
```
3535

@@ -39,7 +39,10 @@ dependencies {
3939
Given an exisiting data class:
4040
- add `@DataCompat` annotation
4141
- mark class private
42-
- Append `Data` to the class name
42+
- append `Data` to the class name
43+
- support default parameters by using `@Default` annotation
44+
- retain existing class annotations (but not parameters for them)
45+
- retain existing interfaces
4346

4447
For example:
4548

@@ -57,101 +60,120 @@ private data class PersonData(val name: String, val nickname: String? = null, va
5760
After compilation, the following class will be generated:
5861

5962
```kotlin
63+
package com.tobrun.`data`.compat.example
64+
65+
import java.util.Objects
66+
import kotlin.Any
67+
import kotlin.Boolean
68+
import kotlin.Int
69+
import kotlin.String
70+
import kotlin.Unit
71+
import kotlin.jvm.JvmSynthetic
72+
6073
/**
6174
* Represents a person.
6275
* @property name The full name.
6376
* @property nickname The nickname.
6477
* @property age The age.
6578
*/
79+
@SampleAnnotation
6680
public class Person private constructor(
67-
public val name: String,
68-
public val nickname: String?,
69-
public val age: Int
70-
) {
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
81+
public val name: String,
82+
public val nickname: String?,
83+
public val age: Int
84+
) : SampleInterface {
85+
public override fun toString() = """Person(name=$name, nickname=$nickname,
86+
age=$age)""".trimIndent()
87+
88+
public override fun equals(other: Any?): Boolean {
89+
if (this === other) return true
90+
if (javaClass != other?.javaClass) return false
91+
other as Person
92+
return name == other.name && nickname == other.nickname && age == other.age
10893
}
10994

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-
}
95+
public override fun hashCode(): Int = Objects.hash(name, nickname, age)
12096

12197
/**
122-
* Set the age.
123-
*
124-
* @param age the age.
125-
* @return Builder
98+
* Convert to Builder allowing to change class properties.
12699
*/
127-
public fun setAge(age: Int?): Builder {
128-
this.age = age
129-
return this
130-
}
100+
public fun toBuilder(): Builder = Builder() .setName(name) .setNickname(nickname) .setAge(age)
131101

132102
/**
133-
* Returns a [Person] reference to the object being constructed by the builder.
103+
* Composes and builds a [Person] object.
134104
*
135-
* Throws an [IllegalArgumentException] when a non-null property wasn't initialised.
105+
* This is a concrete implementation of the builder design pattern.
136106
*
137-
* @return Person
107+
* @property name The full name.
108+
* @property nickname The nickname.
109+
* @property age The age.
138110
*/
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!!)
111+
public class Builder {
112+
@set:JvmSynthetic
113+
public var name: String? = "John"
114+
115+
@set:JvmSynthetic
116+
public var nickname: String? = null
117+
118+
@set:JvmSynthetic
119+
public var age: Int? = 42
120+
121+
/**
122+
* Set the full name.
123+
*
124+
* @param name the full name.
125+
* @return Builder
126+
*/
127+
public fun setName(name: String?): Builder {
128+
this.name = name
129+
return this
130+
}
131+
132+
/**
133+
* Set the nickname.
134+
*
135+
* @param nickname the nickname.
136+
* @return Builder
137+
*/
138+
public fun setNickname(nickname: String?): Builder {
139+
this.nickname = nickname
140+
return this
141+
}
142+
143+
/**
144+
* Set the age.
145+
*
146+
* @param age the age.
147+
* @return Builder
148+
*/
149+
public fun setAge(age: Int?): Builder {
150+
this.age = age
151+
return this
152+
}
153+
154+
/**
155+
* Returns a [Person] reference to the object being constructed by the builder.
156+
*
157+
* Throws an [IllegalArgumentException] when a non-null property wasn't initialised.
158+
*
159+
* @return Person
160+
*/
161+
public fun build(): Person {
162+
if (name==null) {
163+
throw IllegalArgumentException("Null name found when building Person.")
164+
}
165+
if (age==null) {
166+
throw IllegalArgumentException("Null age found when building Person.")
167+
}
168+
return Person(name!!, nickname, age!!)
169+
}
147170
}
148-
}
149171
}
150172

151173
/**
152174
* Creates a [Person] through a DSL-style builder.
153175
*
154-
* @param initializer the intialisation block
176+
* @param initializer the initialisation block
155177
* @return Person
156178
*/
157179
@JvmSynthetic
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.tobrun.datacompat.annotation
2+
3+
/**
4+
* Default value represented as a String.
5+
* Workaround for KSP not supporting class default parameters:
6+
* https://github.com/google/ksp/issues/642
7+
*
8+
* Could be applied only for passing default values in the [DataCompat] class.
9+
*
10+
* @param valueAsString exact representation of the default value. E.g. if default [String] is used,
11+
* it should be passed here as "\"STRING_VALUE\""; if default [Int] is used, it should be passed
12+
* as "INT_VALUE".
13+
*/
14+
@Retention(AnnotationRetention.RUNTIME)
15+
@Target(AnnotationTarget.VALUE_PARAMETER)
16+
annotation class Default(val valueAsString: String)
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.tobrun.data.compat.example
22

33
import com.tobrun.datacompat.annotation.DataCompat
4+
import com.tobrun.datacompat.annotation.Default
5+
6+
interface SampleInterface
7+
annotation class SampleAnnotation
48

59
/**
610
* Represents a person.
@@ -9,4 +13,11 @@ import com.tobrun.datacompat.annotation.DataCompat
913
* @property age The age.
1014
*/
1115
@DataCompat
12-
private data class PersonData(val name: String, val nickname: String? = null, val age: Int)
16+
@SampleAnnotation
17+
private data class PersonData(
18+
@Default("\"John\"")
19+
val name: String,
20+
val nickname: String?,
21+
@Default("42")
22+
val age: Int
23+
) : SampleInterface

processor/src/main/kotlin/com/tobrun/datacompat/DataCompatProcessor.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import com.squareup.kotlinpoet.ksp.toTypeName
2929
import com.squareup.kotlinpoet.ksp.toTypeParameterResolver
3030
import com.squareup.kotlinpoet.ksp.writeTo
3131
import com.tobrun.datacompat.annotation.DataCompat
32+
import com.tobrun.datacompat.annotation.Default
3233
import java.util.Locale
3334

3435
/**
@@ -187,18 +188,46 @@ class DataCompatProcessor(
187188
.returns(Int::class)
188189
.build()
189190
)
191+
192+
// Function toBuilder
193+
addFunction(
194+
FunSpec.builder("toBuilder")
195+
.addKdoc(
196+
"""
197+
Convert to Builder allowing to change class properties.
198+
""".trimIndent()
199+
)
200+
.addStatement(
201+
propertyMap.keys.joinToString(
202+
prefix = "return Builder() .",
203+
transform = { str ->
204+
"set${str.toString().replaceFirstChar {
205+
if (it.isLowerCase())
206+
it.titlecase(Locale.getDefault())
207+
else it.toString()
208+
}}($str)"
209+
},
210+
separator = " .",
211+
)
212+
)
213+
.returns(ClassName("", "Builder"))
214+
.build()
215+
)
190216
}
191217

192218
// Builder pattern
193219
val builderBuilder = TypeSpec.classBuilder("Builder")
194220
for (property in propertyMap) {
195221
val propertyName = property.key.toString()
222+
val defaultValue = property.key.annotations
223+
.firstOrNull { it.annotationType.resolve().toString() == Default::class.simpleName }
224+
?.arguments?.first()
196225
val nullableType = property.value.copy(nullable = true)
197226
builderBuilder.addProperty(
198227
PropertySpec.builder(propertyName, nullableType)
199228
.initializer(
200229
CodeBlock.builder()
201-
.add("null")
230+
.add((defaultValue?.value as? String?) ?: "null")
202231
.build()
203232
)
204233
.addAnnotation(

processor/src/test/kotlin/com/tobrun/datacompat/DataCompatProcessorTest.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ private val simpleTest = SourceFile.kotlin(
1414
"PersonData.kt",
1515
"""
1616
import com.tobrun.datacompat.annotation.DataCompat
17+
import com.tobrun.datacompat.annotation.Default
1718
1819
interface EmptyInterface
1920
interface EmptyInterface2
@@ -27,8 +28,11 @@ interface EmptyInterface2
2728
@Deprecated
2829
@DataCompat
2930
private data class PersonData(
31+
@Default("\"John\"")
3032
val name: String,
31-
val nickname: String? = null,
33+
@Default("null")
34+
val nickname: String?,
35+
@Default("23")
3236
val age: Int,
3337
val veryLongAndVeryDetailedDescription: String?
3438
) : EmptyInterface, EmptyInterface2

processor/src/test/kotlin/com/tobrun/datacompat/SimpleTestContent.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ internal val expectedSimpleTestContent = """
3737
3838
public override fun hashCode(): Int = Objects.hash(name, nickname, age,
3939
veryLongAndVeryDetailedDescription)
40+
41+
/**
42+
* Convert to Builder allowing to change class properties.
43+
*/
44+
public fun toBuilder(): Builder = Builder() .setName(name) .setNickname(nickname) .setAge(age)
45+
.setVeryLongAndVeryDetailedDescription(veryLongAndVeryDetailedDescription)
4046
4147
/**
4248
* Composes and builds a [Person] object.
@@ -50,13 +56,13 @@ internal val expectedSimpleTestContent = """
5056
*/
5157
public class Builder {
5258
@set:JvmSynthetic
53-
public var name: String? = null
59+
public var name: String? = "John"
5460
5561
@set:JvmSynthetic
5662
public var nickname: String? = null
5763
5864
@set:JvmSynthetic
59-
public var age: Int? = null
65+
public var age: Int? = 23
6066
6167
@set:JvmSynthetic
6268
public var veryLongAndVeryDetailedDescription: String? = null

processor/src/test/kotlin/com/tobrun/datacompat/TestAnnotation.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,9 @@ package com.tobrun.datacompat.annotation
1010
@Retention(AnnotationRetention.RUNTIME)
1111
@Target(AnnotationTarget.CLASS)
1212
annotation class DataCompat
13+
14+
@Retention(AnnotationRetention.RUNTIME)
15+
@Target(AnnotationTarget.CLASS)
16+
annotation class Default(val valueAsString: String)
1317
""".trimIndent()
1418
)

0 commit comments

Comments
 (0)