Skip to content
This repository was archived by the owner on Feb 26, 2023. It is now read-only.

Commit 4ece050

Browse files
Merge pull request #4 from ThinkingLogic/develop
Merging develop (v 1.2.0) into master
2 parents 3961614 + 50a5107 commit 4ece050

24 files changed

+398
-63
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
.gradle
22
.idea
33
**/build/
4+
*.iml
5+
/kotlin-builder-example-usage/.kotlintest/
46

57
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
68
!gradle-wrapper.jar

README.md

+80-7
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,55 @@ This project aims to be a minimal viable replacement for the Lombok @Builder plu
66
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
77

88
## Usage
9-
TODO: gradle and maven
9+
#### Import `kotlin-builder-annotation` and `kotlin-builder-processor`
10+
And configure the [Kotlin annotation processor (kapt)](https://kotlinlang.org/docs/reference/kapt.html).
11+
##### Gradle
12+
```gradle
13+
...
14+
apply plugin: 'kotlin-kapt'
15+
...
16+
dependencies {
17+
...
18+
implementation 'com.thinkinglogic.builder:kotlin-builder-annotation:1.2.0'
19+
kapt 'com.thinkinglogic.builder:kotlin-builder-processor:1.2.0'
20+
}
21+
```
22+
##### Maven
23+
```maven
24+
...
25+
<dependencies>
26+
<dependency>
27+
<groupId>com.thinkinglogic.builder</groupId>
28+
<artifactId>kotlin-builder-annotation</artifactId>
29+
<version>1.2.0</version>
30+
</dependency>
31+
...
32+
</dependencies>
33+
...
34+
<execution>
35+
<id>kapt</id>
36+
<goals>
37+
<goal>kapt</goal>
38+
</goals>
39+
<configuration>
40+
<sourceDirs>
41+
<sourceDir>src/main/kotlin</sourceDir>
42+
<sourceDir>src/main/java</sourceDir>
43+
</sourceDirs>
44+
<annotationProcessorPaths>
45+
<!-- Specify your annotation processors here. -->
46+
<annotationProcessorPath>
47+
<groupId>com.thinkinglogic.builder</groupId>
48+
<artifactId>kotlin-builder-processor</artifactId>
49+
<version>1.2.0</version>
50+
</annotationProcessorPath>
51+
</annotationProcessorPaths>
52+
</configuration>
53+
</execution>
1054
11-
#### Annotate your class with the @Builder annotation
55+
```
56+
57+
#### Annotate your class(es) with the @Builder annotation
1258
```kotlin
1359
import com.thinkinglogic.builder.annotation.Builder
1460

@@ -20,8 +66,8 @@ data class MyDataClass(
2066
```
2167
That's it! Client code can now use a builder to construct instances of your class.
2268

23-
Unlike Lombok there's no bytecode manipulation, so we don't have a `MyDataClass.builder()` static method.
24-
Instead we create a `new MyDataClassBuilder()`:
69+
Unlike Lombok there's no bytecode manipulation, so we don't expose a `MyDataClass.builder()` static method.
70+
Instead clients create a `new MyDataClassBuilder()`, for instance:
2571

2672
```java
2773
public class MyDataFactory {
@@ -41,6 +87,9 @@ The builder will check for required fields, so
4187
`new MyDataClassBuilder().notNullString("Foo").build();`
4288
would return a new instance with a null value for 'nullableString'.
4389

90+
To replace Kotlin's `copy()` (and Lombok's `toBuilder()`) method, clients can pass an instance of the annotated class when constructing a builder:
91+
`new MyDataClassBuilder(myDataClassInstance)` - the builder will be initialised with values from the instance.
92+
4493
#### Default values
4594
Kotlin doesn't retain information about default values after compilation, so it cannot be accessed during annotation processing.
4695
Instead we must use the `@DefaultValue` annotation to tell the builder about it:
@@ -72,7 +121,7 @@ data class MyDataClass(
72121
```
73122

74123
#### Mutable collections
75-
Information about the mutability of collections and maps is lost during compilation, so there is a `@Mutable` annotation:
124+
Information about the mutability of collections and maps is lost during compilation, so there is an `@Mutable` annotation:
76125
```kotlin
77126
import com.thinkinglogic.builder.annotation.Builder
78127
import com.thinkinglogic.builder.annotation.Mutable
@@ -85,7 +134,7 @@ data class MyDataClass(
85134
```
86135

87136
#### Constructor parameters
88-
The `@Builder` annotation should be placed on a constructor instead of the class if you have constructor-only parameters:
137+
The `@Builder` annotation may be placed on a constructor instead of the class - useful if you have constructor-only parameters:
89138
```kotlin
90139
import com.thinkinglogic.builder.annotation.Builder
91140

@@ -98,8 +147,32 @@ constructor(
98147
) {
99148
val fullName = "$forename $surname"
100149
}
101-
```
150+
```
151+
152+
#### builder() and toBuilder() methods
153+
The `@Builder` annotation processor cannot modify bytecode, so it cannot generate builder() and toBuilder() methods for you,
154+
but you can add them yourself:
155+
```kotlin
156+
import com.thinkinglogic.builder.annotation.Builder
157+
158+
@Builder
159+
data class MyDataClass(
160+
val notNullString: String,
161+
val nullableString: String?
162+
) {
163+
164+
fun toBuilder(): MyDataClassBuilder = MyDataClassBuilder(this)
165+
166+
companion object {
167+
@JvmStatic fun builder() = MyDataClassBuilder()
168+
}
169+
}
170+
```
171+
`MyDataClass.builder()` and `myDataClassObject.toBuilder()` can now be invoked from java,
172+
enabling a complete drop-in replacement for the Lombok @Builder annotation.
102173

174+
---
175+
Examples of all of the above may be found in the kotlin-builder-example-usage sub-project.
103176
## License
104177
This software is Licenced under the [MIT License](LICENSE.md).
105178

build.gradle

+17-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
buildscript {
2-
ext.kotlin_version = '1.2.60'
2+
ext.kotlin_version = '1.3.50'
3+
ext.kotlintest_version = '3.3.2'
4+
ext.junit_jupiter_version = '5.3.1'
35

46
repositories {
57
mavenCentral()
@@ -12,17 +14,19 @@ buildscript {
1214
}
1315
}
1416

15-
allprojects {
16-
group = 'com.thinkinglogic'
17-
version = '1.0.0'
18-
}
19-
17+
/*
18+
* to publish:
19+
* gradle clean publish bintrayUpload
20+
*/
2021

2122
allprojects {
23+
group = 'com.thinkinglogic.builder'
24+
version = '1.2.0'
2225
apply plugin: "kotlin"
2326

2427
repositories {
2528
mavenCentral()
29+
jcenter()
2630
}
2731

2832
compileKotlin {
@@ -41,6 +45,13 @@ allprojects {
4145

4246
dependencies {
4347
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
48+
testCompile "org.junit.jupiter:junit-jupiter-engine:$junit_jupiter_version"
49+
testCompile "org.junit.jupiter:junit-jupiter-api:$junit_jupiter_version"
50+
testCompile "io.kotlintest:kotlintest-runner-junit5:$kotlintest_version"
51+
testCompile "io.kotlintest:kotlintest-core:$kotlintest_version"
4452
}
4553

54+
test {
55+
useJUnitPlatform()
56+
}
4657
}
+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
#Sun Aug 05 16:37:36 BST 2018
21
distributionBase=GRADLE_USER_HOME
32
distributionPath=wrapper/dists
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip

kotlin-builder-annotation/build.gradle

+8-3
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ dependencies {
77

88
task sourcesJar(type: Jar) {
99
from sourceSets.main.allSource
10-
classifier = 'sources'
10+
archiveClassifier = 'sources'
1111
}
1212

1313
task javadocJar(type: Jar) {
1414
from javadoc
15-
classifier = 'javadoc'
15+
archiveClassifier = 'javadoc'
1616
}
1717

1818
publishing {
1919
publications {
2020
MyPublication(MavenPublication) {
21-
artifactId = 'kotlin-builder-annotation'
21+
groupId 'com.thinkinglogic.builder'
22+
artifactId 'kotlin-builder-annotation'
2223
from components.java
2324
artifact sourcesJar
2425
artifact javadocJar
@@ -61,6 +62,7 @@ javadoc {
6162
options.addBooleanOption('html4', true)
6263
}
6364
}
65+
/* see: https://github.com/bintray/gradle-bintray-plugin */
6466
bintray {
6567
user = System.getenv('BINTRAY_USER')
6668
key = System.getenv('BINTRAY_API_KEY')
@@ -78,6 +80,9 @@ bintray {
7880
released = new Date()
7981
vcsTag = project.version
8082
attributes = ['kotlin-builder-annotation': 'com.thinkinglogic:com.thinkinglogic.builder:kotlin-builder-annotation']
83+
gpg {
84+
sign = true
85+
}
8186
}
8287
}
8388
}
+5-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
apply plugin: 'kotlin-kapt'
22

33
dependencies {
4-
compile project(':kotlin-builder-annotation')
5-
6-
kapt project(':kotlin-builder-processor')
7-
4+
implementation project(':kotlin-builder-annotation')
85
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
96
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
107

11-
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.2.0'
12-
testCompile group: 'com.willowtreeapps.assertk', name: 'assertk', version: '0.10'
13-
testCompile group: 'org.assertj', name: 'assertj-core', version: '3.10.0'
8+
kapt project(':kotlin-builder-processor')
9+
10+
testCompile "org.assertj:assertj-core:3.10.0"
11+
testCompile "com.willowtreeapps.assertk:assertk:0.10"
1412

1513
}

kotlin-builder-example-usage/src/main/kotlin/com/thinkinglogic/example/ArraysDataClass.kt

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ data class ArraysDataClass(
1414
val arrayOfDates: Array<LocalDate>
1515

1616
) {
17+
// Due to the way the JVM uses instance equality for arrays, we should override equals and hashcode
1718
override fun equals(other: Any?): Boolean {
1819
if (this === other) return true
1920
if (javaClass != other?.javaClass) return false

kotlin-builder-example-usage/src/main/kotlin/com/thinkinglogic/example/ClassWithConstructorParameters.kt

+4
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,8 @@ constructor(
2929
result = 31 * result + fullName.hashCode()
3030
return result
3131
}
32+
33+
override fun toString(): String {
34+
return "ClassWithConstructorParameters(otherName=$otherName, fullName='$fullName')"
35+
}
3236
}

kotlin-builder-example-usage/src/main/kotlin/com/thinkinglogic/example/CollectionsDataClass.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import java.util.*
88
@Builder
99
data class CollectionsDataClass(
1010
val listOfStrings: List<String>,
11+
@NullableType val listOfNullableStrings: List<String?>,
1112
val setOfLongs: Set<Long>,
1213
@NullableType val setOfNullableLongs: Set<Long?>,
1314
val hashSet: HashSet<Long>,
1415
val collectionOfDates: Collection<LocalDate>,
1516
@NullableType val mapOfStringToNullableDates: Map<String, LocalDate?>,
1617
val treeMap: TreeMap<String, LocalDate>
17-
1818
)

kotlin-builder-example-usage/src/main/kotlin/com/thinkinglogic/example/DataClassWithAdditionalFields.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import com.thinkinglogic.builder.annotation.Builder
44

55
@Builder
66
data class DataClassWithAdditionalFields(
7-
val constructorString: String
7+
val constructorString: String,
8+
private val privateString: String
89
) {
910
val nonConstructorString = constructorString + "foo"
1011

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.thinkinglogic.example
2+
3+
import com.thinkinglogic.builder.annotation.Builder
4+
import com.thinkinglogic.builder.annotation.DefaultValue
5+
import java.time.LocalDate
6+
7+
@Builder
8+
data class DataClassWithLongPropertyNames(
9+
@DefaultValue("myDefault") val stringWithAVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongNameThatWouldCauseLineWrappingInTheGeneratedFile: String = "myDefault",
10+
val nullableString: String?
11+
) {
12+
13+
/**
14+
* @return a Builder initialised with fields from this object.
15+
*/
16+
fun toBuilder() = DataClassWithLongPropertyNamesBuilder(this)
17+
18+
companion object {
19+
@JvmStatic
20+
fun builder() = DataClassWithLongPropertyNamesBuilder()
21+
}
22+
}

kotlin-builder-example-usage/src/main/kotlin/com/thinkinglogic/example/SimpleDataClass.kt

+12-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,15 @@ data class SimpleDataClass(
1313
val date: LocalDate,
1414
@DefaultValue("withDefaultValue") val stringWithDefault: String = "withDefaultValue",
1515
@DefaultValue("LocalDate.MIN") val defaultDate: LocalDate = LocalDate.MIN
16-
)
16+
) {
17+
18+
/**
19+
* @return a Builder initialised with fields from this object.
20+
*/
21+
fun toBuilder() = SimpleDataClassBuilder(this)
22+
23+
companion object {
24+
@JvmStatic
25+
fun builder() = SimpleDataClassBuilder()
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.thinkinglogic.example;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.time.LocalDate;
6+
import java.util.*;
7+
8+
import static java.util.Arrays.asList;
9+
import static java.util.Collections.emptyList;
10+
import static java.util.Collections.emptySet;
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
import static org.assertj.core.api.Assertions.catchThrowable;
13+
import static org.assertj.core.api.BDDAssertions.then;
14+
15+
public class CollectionsDataClassJavaTest {
16+
17+
@Test
18+
void builderShouldAllowNullValuesInCollections() {
19+
// given
20+
CollectionsDataClassBuilder builder = new CollectionsDataClassBuilder();
21+
22+
List<String> nullableList = asList(null, null, "foo");
23+
Set<Long> nullableSet = new HashSet<>(asList(null, null, 1L));
24+
25+
HashMap<String, LocalDate> nullableMap = new HashMap<>();
26+
nullableMap.put("Foo", null);
27+
28+
29+
// when
30+
CollectionsDataClass result = builder
31+
.listOfNullableStrings(nullableList)
32+
.mapOfStringToNullableDates(nullableMap)
33+
.setOfNullableLongs(nullableSet)
34+
.collectionOfDates(emptySet())
35+
.listOfStrings(emptyList())
36+
.setOfLongs(emptySet())
37+
.hashSet(new HashSet<>())
38+
.treeMap(new TreeMap<>())
39+
.build();
40+
41+
// then
42+
assertThat(result.getListOfNullableStrings()).isEqualTo(nullableList);
43+
assertThat(result.getMapOfStringToNullableDates()).isEqualTo(nullableMap);
44+
assertThat(result.getSetOfNullableLongs()).isEqualTo(nullableSet);
45+
}
46+
}

0 commit comments

Comments
 (0)