├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── ProjectDeclarations.kt │ ├── ProjectUtils.kt │ ├── Properties.kt │ ├── Publications.kt │ └── PublicationsUtils.kt ├── builder ├── annotations │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── io │ │ └── github │ │ └── jffiorillo │ │ └── builder │ │ └── JvmBuilder.kt ├── processor │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── io │ │ └── github │ │ └── jffiorillo │ │ └── builder │ │ └── processor │ │ ├── AppliedType.kt │ │ ├── JvmBuilderProcessor.kt │ │ ├── TypeResolver.kt │ │ └── Utils.kt └── sample │ ├── build.gradle.kts │ ├── generated-kotlin-sources.gradle.kts │ └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── example │ │ └── Test.kt │ └── test │ └── kotlin │ ├── TestDataClass1Test.java │ └── TestDataClass1TestK.kt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | **/build 2 | **/out 3 | local.properties 4 | .gradle 5 | .idea 6 | *.iml 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jffiorillo@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018 Jose Francisco Fiorillo Verenzuela 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JvmBuilder 2 | [ ![Download](https://api.bintray.com/packages/jffiorillo/jvmbuilder/jvmbuilder/images/download.svg) ](https://bintray.com/jffiorillo/jvmbuilder/jvmbuilder/_latestVersion) 3 | 4 | A source code generator for [Kotlin](https://kotlinlang.org/) data classes to automatically create a Builder class. 5 | 6 | # How to use JvmBuilder 7 | 8 | When annotating a Kotlin `data class` with `@JvmBuilder` 9 | ``` 10 | @JvmBuilder 11 | data class Test(val foo: Int = 1, val bar: String) 12 | ``` 13 | 14 | The following class is generated 15 | ``` 16 | // Code auto-generated by JvmBuilder. Do not edit. 17 | package com.example 18 | 19 | import kotlin.Int 20 | import kotlin.String 21 | 22 | class JvmBuilder_Test { 23 | private var foo: Int? = null 24 | 25 | private var bar: String? = null 26 | 27 | fun foo(foo: Int): JvmBuilder_Test { 28 | this.foo = foo 29 | return this 30 | } 31 | 32 | fun bar(bar: String): JvmBuilder_Test { 33 | this.bar = bar 34 | return this 35 | } 36 | 37 | fun build(): Test { 38 | var result = com.example.Test(bar = this.bar!!) 39 | result = result.copy(foo = this.foo ?: result.foo) 40 | return result 41 | } 42 | } 43 | ``` 44 | 45 | This provides a `Builder` class that can be used in Java to create your Kotlin `Test` `data class` using the default values and following a [`Builder Pattern`](https://en.wikipedia.org/wiki/Builder_pattern). 46 | 47 | The following Java code generates a `Test` instance with `foo = 1` (taking the 1 from the default `Kotlin` constructor) and `bar = "bar"`. 48 | ``` 49 | new Jvm_Builder().bar("bar").build() 50 | ``` 51 | 52 | ## Gradle 53 | 54 | Gradle users should add the dependencies in their `build.gradle` file: 55 | 56 | ``` 57 | dependencies { 58 | implementation "io.github.jffiorillo:jvmbuilder-annotations:" 59 | kapt "io.github.jffiorillo:jvmbuilder:" 60 | } 61 | ``` 62 | 63 | ## Maven 64 | 65 | Maven users should add the dependencies in their `pom.xml` file: 66 | 67 | ``` 68 | 69 | io.github.jffiorillo 70 | jvmbuilder-annotations 71 | {latest_version} 72 | 73 | 74 | io.github.jffiorillo 75 | jvmbuilder 76 | {latest_version} 77 | provided 78 | 79 | ``` 80 | 81 | 82 | ## Benefit 83 | 84 | * In order to have a good Kotlin interoperability with [Java](https://en.wikipedia.org/wiki/Java_(programming_language)), you can use 85 | this tool to generate automatically generate Builders for your data classes that are accessible from Java consumers. 86 | * The time of executing the annotation processor is really slow because uses the [kotlin-metadata](https://github.com/Takhion/kotlin-metadata) instead of reflection. 87 | 88 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { kotlin("jvm") version "1.2.40" apply false } 2 | 3 | subprojects { 4 | repositories { 5 | jcenter() 6 | } 7 | } -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { `kotlin-dsl` } 2 | 3 | repositories { jcenter() } 4 | 5 | dependencies { 6 | compileOnly(kotlin("gradle-plugin")) 7 | compile("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0") 8 | } 9 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/ProjectDeclarations.kt: -------------------------------------------------------------------------------- 1 | import com.jfrog.bintray.gradle.BintrayExtension 2 | import org.gradle.api.Project 3 | import org.gradle.api.plugins.JavaPluginConvention 4 | import org.gradle.api.publish.PublishingExtension 5 | import org.gradle.api.tasks.SourceSet 6 | import org.gradle.api.tasks.SourceSetContainer 7 | import org.gradle.kotlin.dsl.get 8 | 9 | val publishing = configuration() 10 | val bintray = configuration() 11 | 12 | val Project.java by extension() 13 | 14 | val SourceSetContainer.main: SourceSet get() = this["main"] 15 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/ProjectUtils.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Plugin 2 | import org.gradle.api.Project 3 | import org.gradle.kotlin.dsl.configure 4 | import org.gradle.kotlin.dsl.the 5 | import kotlin.properties.ReadOnlyProperty 6 | import kotlin.reflect.KProperty 7 | 8 | typealias ProjectExt0 = Project.() -> R 9 | typealias ProjectExt1 = Project.(T) -> R 10 | 11 | inline fun extension(): ProjectExt0 = { the() } 12 | inline fun configuration(): ProjectExt1 Unit, Unit> = { configure(it) } 13 | 14 | inline fun > Project.applyPlugin() = apply { plugin(T::class.java) } 15 | 16 | operator fun (This.() -> R).getValue(thisRef: This, property: KProperty<*>) = invoke(thisRef) 17 | 18 | fun extraOrEnv(envName: String) = 19 | findProjectProperty { it as? String ?: System.getenv(envName) } 20 | 21 | fun extraOrDefault(defaultValue: Boolean) = 22 | findProjectProperty { 23 | when { 24 | it is Boolean -> it 25 | it is String && it.equals("true", ignoreCase = true) -> true 26 | it is String && it.equals("false", ignoreCase = true) -> false 27 | else -> defaultValue 28 | } 29 | } 30 | 31 | inline fun findProjectProperty(crossinline transformValue: (Any?) -> T) = 32 | readOnlyProperty { findProperty(it.name).let(transformValue) } 33 | 34 | inline fun readOnlyProperty(crossinline getValue: This.(property: KProperty<*>) -> T) = 35 | object : ReadOnlyProperty { 36 | override fun getValue(thisRef: This, property: KProperty<*>): T = getValue(thisRef, property) 37 | } 38 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Properties.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MayBeConstant") 2 | 3 | import org.gradle.api.Project 4 | 5 | val codeVersion = "0.0.3" 6 | 7 | val gitHubUser = "jffiorillo" 8 | val gitHubRepo = "jvmbuilder" 9 | val gitHubRepoDomain = "github.com/$gitHubUser/$gitHubRepo" 10 | 11 | val gitTag = "release/$codeVersion" 12 | val gitRepo = "$gitHubRepoDomain.git" 13 | 14 | val mainRepoUrl = "https://$gitHubRepoDomain" 15 | val taggedRepoUrl = "$mainRepoUrl/tree/$gitTag" 16 | 17 | val Projects.libName 18 | get() = when (this) { 19 | Projects.annotations -> "jvmbuilder-annotations" 20 | Projects.processor -> "jvmbuilder" 21 | } 22 | val libDescription = "A source code generator for Kotlin data classes to automatically create a Builder class." 23 | val libUrl = mainRepoUrl 24 | 25 | val libGroupId = "io.github.jffiorillo" 26 | val libVersion = codeVersion 27 | val Projects.libArtifactId 28 | get() = when (this) { 29 | Projects.annotations -> "jvmbuilder-annotations" 30 | Projects.processor -> "jvmbuilder" 31 | } 32 | 33 | val Projects.publicationName 34 | get() = this.libArtifactId.split("-").joinToString("") { it -> it.capitalize() }.decapitalize() 35 | val Projects.publicationTaskName 36 | get() = "publish${publicationName.capitalize()}PublicationToMavenRepository" 37 | 38 | val authorName = "Jose Francisco Fiorillo" 39 | 40 | val licenseName = "Apache-2.0" 41 | val Project.licenseFile get() = rootDir.resolve("LICENSE") 42 | val Project.licenseUrl get() = "$mainRepoUrl/blob/$gitTag/${licenseFile.toRelativeString(rootDir)}" 43 | 44 | val issuesSystem = "GitHub" 45 | val issuesUrl = "$mainRepoUrl/issues" 46 | 47 | val bintrayRepo = "jvmbuilder" 48 | val bintrayTags = arrayOf("kotlin", "jvmbuilder") 49 | 50 | val Project.bintrayPublish by extraOrDefault(true) 51 | val Project.bintrayOverride by extraOrDefault(false) 52 | val Project.bintrayDryRun by extraOrDefault(false) 53 | val Project.bintrayGpgSign by extraOrDefault(true) 54 | val Project.bintrayMavenCentralSync by extraOrDefault(true) 55 | val Project.bintrayMavenCentralClose by extraOrDefault(true) 56 | 57 | val Project.bintrayUser by extraOrEnv("BINTRAY_USER") 58 | val Project.bintrayKey by extraOrEnv("BINTRAY_KEY") 59 | 60 | val Project.sonatypeUser by extraOrEnv("SONATYPE_USER") 61 | val Project.sonatypePassword by extraOrEnv("SONATYPE_PASSWORD") 62 | 63 | val Project.outputDir get() = buildDir.resolve("out") 64 | 65 | enum class Projects { annotations, processor } 66 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Publications.kt: -------------------------------------------------------------------------------- 1 | import com.jfrog.bintray.gradle.Artifact 2 | import com.jfrog.bintray.gradle.BintrayPlugin 3 | import org.gradle.api.Project 4 | import org.gradle.api.Task 5 | import org.gradle.api.plugins.JavaLibraryPlugin 6 | import org.gradle.api.publish.maven.MavenPublication 7 | import org.gradle.api.publish.maven.plugins.MavenPublishPlugin 8 | import org.gradle.api.tasks.bundling.Jar 9 | import org.gradle.kotlin.dsl.get 10 | import org.gradle.kotlin.dsl.invoke 11 | import org.gradle.kotlin.dsl.task 12 | 13 | fun Project.configurePublications(projectType: Projects, uploadTaskName: String = "upload"): Task { 14 | 15 | 16 | applyPlugin() 17 | applyPlugin() 18 | applyPlugin() 19 | 20 | val sourcesJar = task("sourcesJar") { 21 | from(java.sourceSets.main.allSource) 22 | classifier = "sources" 23 | } 24 | 25 | val javadocJar = task("javadocJar") { 26 | classifier = "javadoc" 27 | } 28 | 29 | publishing { 30 | repositories.maven { url = uri(outputDir) } 31 | (publications) { 32 | projectType.publicationName(MavenPublication::class) { 33 | from(components["java"]) 34 | artifact(sourcesJar){ classifier = "sources" } 35 | artifact(javadocJar){ classifier = "javadoc" } 36 | 37 | groupId = libGroupId 38 | artifactId = projectType.libArtifactId 39 | version = libVersion 40 | pom.buildXml { 41 | "name"..projectType.libName 42 | "description"..libDescription 43 | "url"..libUrl 44 | "licenses" { 45 | "license" { 46 | "name"..licenseName 47 | "url"..licenseUrl 48 | } 49 | } 50 | "issueManagement" { 51 | "system"..issuesSystem 52 | "url"..issuesUrl 53 | } 54 | "developers" { 55 | "developer" { 56 | "name"..authorName 57 | } 58 | } 59 | "scm" { 60 | "connection".."scm:git:git://$gitRepo" 61 | "developerConnection".."scm:git:ssh://$gitRepo" 62 | "tag"..gitTag 63 | "url"..taggedRepoUrl 64 | } 65 | } 66 | } 67 | } 68 | } 69 | 70 | bintray { 71 | publish = bintrayPublish 72 | override = bintrayOverride 73 | dryRun = bintrayDryRun 74 | user = bintrayUser 75 | key = bintrayKey 76 | filesSpec { 77 | fileUploads = fileTree(outputDir).map { 78 | Artifact().apply { 79 | file = it 80 | setPath(it.toRelativeString(outputDir)) 81 | } 82 | } 83 | } 84 | pkg { 85 | repo = bintrayRepo 86 | name = projectType.libName 87 | desc = libDescription 88 | websiteUrl = libUrl 89 | userOrg = user 90 | issueTrackerUrl = issuesUrl 91 | githubRepo = "$gitHubUser/$gitHubRepo" 92 | vcsUrl = "https://$gitRepo" 93 | setLabels(*bintrayTags) 94 | setLicenses(licenseName) 95 | version { 96 | name = libVersion 97 | vcsTag = gitTag 98 | gpg.sign = bintrayGpgSign 99 | mavenCentralSync { 100 | sync = bintrayMavenCentralSync 101 | close = if (bintrayMavenCentralClose) "1" else "0" 102 | user = sonatypeUser 103 | password = sonatypePassword 104 | } 105 | } 106 | } 107 | } 108 | 109 | val uploadTask = task(uploadTaskName) 110 | val bintrayUploadTask = tasks["bintrayUpload"] 111 | 112 | uploadTask.dependsOn(bintrayUploadTask) 113 | bintrayUploadTask.dependsOn(projectType.publicationTaskName) 114 | 115 | return uploadTask 116 | } 117 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/PublicationsUtils.kt: -------------------------------------------------------------------------------- 1 | import com.jfrog.bintray.gradle.BintrayExtension 2 | import com.jfrog.bintray.gradle.BintrayExtension.MavenCentralSyncConfig 3 | import com.jfrog.bintray.gradle.BintrayExtension.PackageConfig 4 | import com.jfrog.bintray.gradle.BintrayExtension.VersionConfig 5 | import com.jfrog.bintray.gradle.RecordingCopyTask 6 | import org.gradle.api.publish.maven.MavenPom 7 | import org.gradle.kotlin.dsl.closureOf 8 | import org.w3c.dom.Document 9 | import org.w3c.dom.Element 10 | import org.w3c.dom.Node 11 | 12 | fun BintrayExtension.filesSpec(configure: RecordingCopyTask.() -> Unit): Any? = filesSpec(closureOf(configure)) 13 | fun BintrayExtension.pkg(configure: PackageConfig.() -> Unit): Any? = pkg(closureOf(configure)) 14 | fun PackageConfig.version(configure: VersionConfig.() -> Unit): Any? = version(closureOf(configure)) 15 | fun VersionConfig.mavenCentralSync(configure: MavenCentralSyncConfig.() -> Unit): Any? = mavenCentralSync(closureOf(configure)) 16 | 17 | inline fun MavenPom.buildXml(crossinline xml: NodeContext.() -> Unit) { 18 | withXml { 19 | val root = asElement() 20 | NodeContext(root, root.ownerDocument).xml() 21 | } 22 | } 23 | 24 | class NodeContext(val node: T, val doc: Document) { 25 | 26 | inline operator fun String.invoke(nodeContent: NodeContext.() -> Unit): Element = 27 | doc.createElement(this) 28 | .also { NodeContext(it, doc).nodeContent() } 29 | .also { node.appendChild(it) } 30 | 31 | operator fun String.rangeTo(textContent: String) = 32 | invoke { node.textContent = textContent } 33 | } 34 | -------------------------------------------------------------------------------- /builder/annotations/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | } 4 | 5 | 6 | val upload = configurePublications(Projects.annotations) -------------------------------------------------------------------------------- /builder/annotations/src/main/kotlin/io/github/jffiorillo/builder/JvmBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Jose Francisco Fiorillo Verenzuela 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.jffiorillo.builder 17 | 18 | import kotlin.annotation.AnnotationTarget.CLASS 19 | 20 | @Target(CLASS) 21 | annotation class JvmBuilder(val prefix: String = "", val debug: Boolean = false) 22 | -------------------------------------------------------------------------------- /builder/processor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | kotlin("kapt") 4 | } 5 | 6 | dependencies { 7 | compile(kotlin("stdlib")) 8 | compileOnly("com.google.auto.service:auto-service:1.0-rc4") 9 | kapt("com.google.auto.service:auto-service:1.0-rc4") 10 | compile(project(":builder:annotations")) 11 | compile("me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0") 12 | 13 | compile("com.squareup:kotlinpoet:1.0.0-RC1") 14 | } 15 | 16 | val upload = configurePublications(Projects.processor) -------------------------------------------------------------------------------- /builder/processor/src/main/kotlin/io/github/jffiorillo/builder/processor/AppliedType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.jffiorillo.builder.processor 17 | 18 | import com.squareup.kotlinpoet.TypeName 19 | import com.squareup.kotlinpoet.TypeVariableName 20 | import com.squareup.kotlinpoet.asTypeName 21 | import javax.lang.model.element.TypeElement 22 | import javax.lang.model.type.DeclaredType 23 | import javax.lang.model.util.Types 24 | 25 | /** 26 | * A concrete type like `List` with enough information to know how to resolve its type 27 | * variables. 28 | */ 29 | internal class AppliedType private constructor( 30 | val element: TypeElement, 31 | val resolver: TypeResolver, 32 | private val mirror: DeclaredType 33 | ) { 34 | /** Returns all supertypes of this, recursively. Includes both interface and class supertypes. */ 35 | fun supertypes( 36 | types: Types, 37 | result: MutableSet = mutableSetOf() 38 | ): Set { 39 | result.add(this) 40 | for (supertype in types.directSupertypes(mirror)) { 41 | val supertypeDeclaredType = supertype as DeclaredType 42 | val supertypeElement = supertypeDeclaredType.asElement() as TypeElement 43 | val appliedSupertype = AppliedType(supertypeElement, 44 | resolver(supertypeElement, supertypeDeclaredType), supertypeDeclaredType) 45 | appliedSupertype.supertypes(types, result) 46 | } 47 | return result 48 | } 49 | 50 | /** Returns a resolver that uses `element` and `mirror` to resolve type parameters. */ 51 | private fun resolver(element: TypeElement, mirror: DeclaredType): TypeResolver { 52 | return object : TypeResolver() { 53 | override fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName { 54 | val index = element.typeParameters.indexOfFirst { 55 | it.simpleName.toString() == typeVariable.name 56 | } 57 | check(index != -1) { "Unexpected type variable $typeVariable in $mirror" } 58 | val argument = mirror.typeArguments[index] 59 | return argument.asTypeName() 60 | } 61 | } 62 | } 63 | 64 | override fun toString() = mirror.toString() 65 | 66 | companion object { 67 | fun get(typeElement: TypeElement): AppliedType { 68 | return AppliedType(typeElement, TypeResolver(), typeElement.asType() as DeclaredType) 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /builder/processor/src/main/kotlin/io/github/jffiorillo/builder/processor/JvmBuilderProcessor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Jose Francisco Fiorillo Verenzuela 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.jffiorillo.builder.processor 17 | 18 | import com.google.auto.service.AutoService 19 | import com.squareup.kotlinpoet.* 20 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 21 | import io.github.jffiorillo.builder.JvmBuilder 22 | import me.eugeniomarletti.kotlin.metadata.* 23 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf 24 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.NameResolver 25 | import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor 26 | import java.io.File 27 | import javax.annotation.processing.Messager 28 | import javax.annotation.processing.Processor 29 | import javax.annotation.processing.RoundEnvironment 30 | import javax.lang.model.SourceVersion 31 | import javax.lang.model.element.Element 32 | import javax.lang.model.element.TypeElement 33 | import javax.tools.Diagnostic.Kind.ERROR 34 | import javax.tools.Diagnostic.Kind.NOTE 35 | 36 | @AutoService(Processor::class) 37 | class JvmBuilderProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils { 38 | 39 | private val annotationName = JvmBuilder::class.java.canonicalName 40 | 41 | private lateinit var metadataHelper: MetadataHelper 42 | 43 | override fun getSupportedAnnotationTypes() = setOf(annotationName) 44 | 45 | override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest() 46 | 47 | override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { 48 | val annotatedElements = roundEnv.getElementsAnnotatedWith(elementUtils.getTypeElement(annotationName)) 49 | if (annotatedElements.isEmpty()) return false 50 | 51 | annotatedElements.filter { it.kotlinMetadata !is KotlinClassMetadata }.forEach { errorMustBeDataClass(it) } 52 | 53 | annotatedElements.filter { it.kotlinMetadata is KotlinClassMetadata }.map { generateBuilder(it) } 54 | return true 55 | } 56 | 57 | private fun generateBuilder(element: Element) { 58 | val metadata = element.kotlinMetadata 59 | 60 | if (metadata !is KotlinClassMetadata) { 61 | errorMustBeDataClass(element) 62 | return 63 | } 64 | 65 | val classData = metadata.data 66 | metadataHelper = MetadataHelper(classData.classProto, classData.nameResolver, element as TypeElement, messager) 67 | with(metadataHelper) { 68 | val fqClassName = getNameUsingNameResolver().replace('/', '.') 69 | val `package` = getNameUsingNameResolver().substringBeforeLast('/').replace('/', '.') 70 | 71 | 72 | val className = fqClassName.substringAfter(`package`).replace(".", "") 73 | 74 | val builderClassName = "JvmBuilder_$className" 75 | val jvmBuilderPrefix = element.getAnnotation(JvmBuilder::class.java).prefix 76 | 77 | val typeArguments = generateParameterizedTypes() 78 | val functionsReturnClass = generateFunctionsReturnClass(typeArguments, "$`package`.$builderClassName") 79 | 80 | val parameters = generatePropertyAndBuilderFunPerProperty(jvmBuilderPrefix, functionsReturnClass) 81 | 82 | 83 | val fileName = "$builderClassName.kt" 84 | val file = File(generatedDir, fileName) 85 | printMessageIfDebug("fileName = $fileName, file = $file") 86 | 87 | val builderFun = generateBuildFunction(classProto, generateBuildFunctionReturnsClass(typeArguments, element), element) 88 | 89 | FileSpec.builder(`package`, builderClassName) 90 | .addComment("Code auto-generated by JvmBuilder. Do not edit.") 91 | .addType(TypeSpec.classBuilder(builderClassName) 92 | .addProperties(parameters.map { it.first }) 93 | .addFunctions(parameters.map { it.second }) 94 | .addFunction(builderFun) 95 | .addTypeVariables(typeArguments) 96 | .build() 97 | ) 98 | .build() 99 | .writeTo(file) 100 | } 101 | } 102 | 103 | private fun generatePropertyAndBuilderFunPerProperty(prefix: String, typeName: TypeName) = with(metadataHelper) { 104 | classProto.constructorList 105 | .single { it.isPrimary } 106 | .valueParameterList 107 | .map { valueParameter -> 108 | val name = valueParameter.getNameUsingNameResolver() 109 | val type = valueParameter.resolveType() 110 | val functionName = if (prefix.isNotEmpty()) prefix + name.capitalize() else name 111 | Pair(PropertySpec.varBuilder(name, 112 | type.resolve(), KModifier.PRIVATE) 113 | .initializer("null").build(), 114 | FunSpec.builder(functionName) 115 | .addParameter(ParameterSpec.builder(name, type).build()) 116 | .returns(typeName) 117 | .addStatement("this.$name = $name") 118 | .addStatement("return this").build() 119 | ) 120 | } 121 | } 122 | 123 | private fun generateParameterizedTypes() = with(metadataHelper) { 124 | classProto.typeParameterList 125 | .map { typeArgument -> 126 | val parameterizedTypeClass = typeArgument.upperBoundOrBuilderList 127 | .map { 128 | when (it) { 129 | is ProtoBuf.Type.Builder -> it.build().resolveType() 130 | is ProtoBuf.Type -> it.resolveType() 131 | else -> { 132 | throw IllegalArgumentException("$it bounds") 133 | } 134 | } 135 | }.toTypedArray() 136 | return@map if (parameterizedTypeClass.isNotEmpty()) 137 | TypeVariableName(typeArgument.getNameUsingNameResolver(), *parameterizedTypeClass) 138 | else 139 | TypeVariableName(typeArgument.getNameUsingNameResolver()) 140 | } 141 | } 142 | 143 | private fun generateBuildFunction(classProto: ProtoBuf.ClassOrBuilder, className: TypeName, element: TypeElement): FunSpec { 144 | with(metadataHelper) { 145 | val creationAssignment = classProto.constructorList 146 | .single { it.isPrimary } 147 | .valueParameterList 148 | .filter { !it.declaresDefaultValue } 149 | .map { valueParameter -> 150 | val name = valueParameter.getNameUsingNameResolver() 151 | val type = valueParameter.resolveType() 152 | return@map "$name = ${if (type.nullable) "this.$name" else "this.$name!!"}" 153 | }.joinToString(prefix = "var result = ${element.asClassName()}(", postfix = ")${System.getProperty("line.separator")}") 154 | 155 | 156 | val useProvidedValuesForDefaultValues = classProto.constructorList 157 | .single { it.isPrimary } 158 | .valueParameterList 159 | .filter { it.declaresDefaultValue } 160 | .map { valueParameter -> 161 | val name = valueParameter.getNameUsingNameResolver() 162 | return@map "$name = this.$name ?: result.$name" 163 | }.joinToString(prefix = "result = result.copy(", postfix = ")${System.getProperty("line.separator")}") 164 | 165 | return FunSpec.builder("build").let { 166 | it.returns(className) 167 | it.addCode(creationAssignment) 168 | if (useProvidedValuesForDefaultValues != "result.copy()") { 169 | it.addCode(useProvidedValuesForDefaultValues) 170 | } 171 | it.addCode("return result${System.getProperty("line.separator")}") 172 | it.build() 173 | } 174 | } 175 | } 176 | 177 | private fun generateFunctionsReturnClass(typeArguments: List, fqBuilderClassName: String): TypeName = 178 | if (typeArguments.isNotEmpty()) 179 | ClassName.bestGuess(fqBuilderClassName).parameterizedBy(*typeArguments.toTypedArray()) 180 | else 181 | ClassName.bestGuess(fqBuilderClassName) 182 | 183 | 184 | private fun generateBuildFunctionReturnsClass(typeArguments: List, element: TypeElement): TypeName = 185 | if (typeArguments.isNotEmpty()) 186 | element.asClassName().parameterizedBy(*typeArguments.toTypedArray()) 187 | else 188 | element.asClassName() 189 | 190 | 191 | private fun errorMustBeDataClass(element: Element) { 192 | messager.printMessage(ERROR, 193 | "@${JvmBuilder::class.java.simpleName} can't be applied to $element: must be a Kotlin data class", element) 194 | } 195 | 196 | 197 | internal class MetadataHelper( 198 | val classProto: ProtoBuf.Class, 199 | private val nameResolver: NameResolver, 200 | private val element: TypeElement, 201 | private val messager: Messager) { 202 | 203 | private val appliedType = AppliedType.get(element) 204 | private val jvmBuilder = element.getAnnotation(JvmBuilder::class.java) 205 | 206 | fun ProtoBuf.ValueParameterOrBuilder.getNameUsingNameResolver() = nameResolver.getString(this.name) 207 | fun ProtoBuf.TypeParameterOrBuilder.getNameUsingNameResolver() = nameResolver.getString(this.name) 208 | fun getNameUsingNameResolver() = nameResolver.getString(classProto.fqName) 209 | fun ProtoBuf.ValueParameterOrBuilder.resolveType() = type.asTypeName(nameResolver, classProto::getTypeParameter, false) 210 | fun ProtoBuf.Type.resolveType() = asTypeName(nameResolver, classProto::getTypeParameter, false) 211 | fun TypeName.resolve() = appliedType.resolver.resolve(this.asNullable()) 212 | 213 | fun printMessageIfDebug(message: String) { 214 | if (jvmBuilder.debug) { 215 | messager.printMessage(NOTE, "JvmBuilder: $message", element) 216 | } 217 | } 218 | 219 | } 220 | 221 | } -------------------------------------------------------------------------------- /builder/processor/src/main/kotlin/io/github/jffiorillo/builder/processor/TypeResolver.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.jffiorillo.builder.processor 17 | 18 | import com.squareup.kotlinpoet.ClassName 19 | import com.squareup.kotlinpoet.ParameterizedTypeName 20 | import com.squareup.kotlinpoet.TypeName 21 | import com.squareup.kotlinpoet.TypeVariableName 22 | import com.squareup.kotlinpoet.WildcardTypeName 23 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 24 | 25 | /** 26 | * Resolves type parameters against a type declaration. Use this to fill in type variables with 27 | * their actual type parameters. 28 | */ 29 | open class TypeResolver { 30 | open fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName = typeVariable 31 | 32 | fun resolve(typeName: TypeName): TypeName { 33 | return when (typeName) { 34 | is ClassName -> typeName 35 | 36 | is ParameterizedTypeName -> { 37 | typeName.rawType.parameterizedBy(*(typeName.typeArguments.map { resolve(it) }.toTypedArray())) 38 | .asNullableIf(typeName.nullable) 39 | } 40 | 41 | is WildcardTypeName -> { 42 | when { 43 | typeName.lowerBounds.size == 1 -> { 44 | WildcardTypeName.supertypeOf(resolve(typeName.lowerBounds[0])) 45 | .asNullableIf(typeName.nullable) 46 | } 47 | typeName.upperBounds.size == 1 -> { 48 | WildcardTypeName.subtypeOf(resolve(typeName.upperBounds[0])) 49 | .asNullableIf(typeName.nullable) 50 | } 51 | else -> { 52 | throw IllegalArgumentException( 53 | "Unrepresentable wildcard type. Cannot have more than one bound: $typeName") 54 | } 55 | } 56 | } 57 | 58 | is TypeVariableName -> resolveTypeVariable(typeName) 59 | 60 | else -> throw IllegalArgumentException("Unrepresentable type: $typeName") 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /builder/processor/src/main/kotlin/io/github/jffiorillo/builder/processor/Utils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.jffiorillo.builder.processor 17 | 18 | import com.squareup.kotlinpoet.* 19 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 20 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf 21 | import me.eugeniomarletti.kotlin.metadata.shadow.metadata.deserialization.NameResolver 22 | 23 | internal fun TypeName.asNullableIf(condition: Boolean): TypeName { 24 | return if (condition) asNullable() else this 25 | } 26 | 27 | internal fun ProtoBuf.TypeParameter.asTypeName( 28 | nameResolver: NameResolver, 29 | getTypeParameter: (index: Int) -> ProtoBuf.TypeParameter, 30 | resolveAliases: Boolean = false 31 | ): TypeVariableName { 32 | val possibleBounds = upperBoundList.map { 33 | it.asTypeName(nameResolver, getTypeParameter, resolveAliases) 34 | } 35 | return if (possibleBounds.isEmpty()) { 36 | TypeVariableName( 37 | name = nameResolver.getString(name), 38 | variance = variance.asKModifier()) 39 | } else { 40 | TypeVariableName( 41 | name = nameResolver.getString(name), 42 | bounds = *possibleBounds.toTypedArray(), 43 | variance = variance.asKModifier()) 44 | } 45 | } 46 | 47 | internal fun ProtoBuf.TypeParameter.Variance.asKModifier(): KModifier? { 48 | return when (this) { 49 | ProtoBuf.TypeParameter.Variance.IN -> KModifier.IN 50 | ProtoBuf.TypeParameter.Variance.OUT -> KModifier.OUT 51 | ProtoBuf.TypeParameter.Variance.INV -> null 52 | } 53 | } 54 | 55 | /** 56 | * Returns the TypeName of this type as it would be seen in the source code, including nullability 57 | * and generic type parameters. 58 | * 59 | * @param [nameResolver] a [NameResolver] instance from the source proto 60 | * @param [getTypeParameter] a function that returns the type parameter for the given index. **Only 61 | * called if [ProtoBuf.Type.hasTypeParameter] is true!** 62 | */ 63 | internal fun ProtoBuf.Type.asTypeName( 64 | nameResolver: NameResolver, 65 | getTypeParameter: (index: Int) -> ProtoBuf.TypeParameter, 66 | useAbbreviatedType: Boolean = true 67 | ): TypeName { 68 | 69 | val argumentList = when { 70 | useAbbreviatedType && hasAbbreviatedType() -> abbreviatedType.argumentList 71 | else -> argumentList 72 | } 73 | 74 | if (hasFlexibleUpperBound()) { 75 | return WildcardTypeName.subtypeOf( 76 | flexibleUpperBound.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType)) 77 | .asNullableIf(nullable) 78 | } else if (hasOuterType()) { 79 | return WildcardTypeName.supertypeOf( 80 | outerType.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType)) 81 | .asNullableIf(nullable) 82 | } 83 | 84 | val realType = when { 85 | hasTypeParameter() -> return getTypeParameter(typeParameter) 86 | .asTypeName(nameResolver, getTypeParameter, useAbbreviatedType) 87 | .asNullableIf(nullable) 88 | hasTypeParameterName() -> typeParameterName 89 | useAbbreviatedType && hasAbbreviatedType() -> abbreviatedType.typeAliasName 90 | else -> className 91 | } 92 | 93 | var typeName: TypeName = 94 | ClassName.bestGuess(nameResolver.getString(realType) 95 | .replace("/", ".")) 96 | 97 | if (argumentList.isNotEmpty()) { 98 | val remappedArgs: Array = argumentList.map { argumentType -> 99 | val nullableProjection = if (argumentType.hasProjection()) { 100 | argumentType.projection 101 | } else null 102 | if (argumentType.hasType()) { 103 | argumentType.type.asTypeName(nameResolver, getTypeParameter, useAbbreviatedType) 104 | .let { argumentTypeName -> 105 | nullableProjection?.let { projection -> 106 | when (projection) { 107 | ProtoBuf.Type.Argument.Projection.IN -> WildcardTypeName.supertypeOf(argumentTypeName) 108 | ProtoBuf.Type.Argument.Projection.OUT -> { 109 | if (argumentTypeName == ANY) { 110 | // This becomes a *, which we actually don't want here. 111 | // List works with List<*>, but List<*> doesn't work with List 112 | argumentTypeName 113 | } else { 114 | WildcardTypeName.subtypeOf(argumentTypeName) 115 | } 116 | } 117 | ProtoBuf.Type.Argument.Projection.STAR -> WildcardTypeName.STAR 118 | ProtoBuf.Type.Argument.Projection.INV -> TODO("INV projection is unsupported") 119 | } 120 | } ?: argumentTypeName 121 | } 122 | } else { 123 | WildcardTypeName.STAR 124 | } 125 | }.toTypedArray() 126 | typeName = (typeName as ClassName).parameterizedBy(*remappedArgs) 127 | } 128 | 129 | return typeName.asNullableIf(nullable) 130 | } 131 | 132 | val ProtoBuf.TypeParameter.varianceModifier: KModifier? 133 | get() { 134 | return variance.asKModifier().let { 135 | // We don't redeclare out variance here 136 | if (it == KModifier.OUT) { 137 | null 138 | } else { 139 | it 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /builder/sample/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | kotlin("kapt") 4 | } 5 | 6 | apply { from("generated-kotlin-sources.gradle.kts") } 7 | 8 | dependencies { 9 | compile(kotlin("stdlib")) 10 | compile(project(":builder:annotations")) 11 | kapt(project(":builder:processor")) 12 | testCompile("org.junit.jupiter:junit-jupiter-api:5.2.0") 13 | testCompile("com.pholser:junit-quickcheck-core:0.8") 14 | testCompile("com.pholser:junit-quickcheck-generators:0.8") 15 | } 16 | -------------------------------------------------------------------------------- /builder/sample/generated-kotlin-sources.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.plugins.ide.idea.model.IdeaModel 2 | import org.gradle.plugins.ide.idea.IdeaPlugin 3 | 4 | apply { plugin(IdeaPlugin::class.java) } 5 | 6 | configure { 7 | module { 8 | val dirs = listOf( 9 | "generated/source/kapt/main", 10 | "generated/source/kaptKotlin/main", 11 | "tmp/kapt/main/kotlinGenerated") 12 | .map(buildDir::resolve) 13 | 14 | listOf(sourceDirs, generatedSourceDirs).forEach { it.addAll(dirs) } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /builder/sample/src/main/kotlin/com/example/Test.kt: -------------------------------------------------------------------------------- 1 | package com.example 2 | 3 | import io.github.jffiorillo.builder.JvmBuilder 4 | 5 | 6 | typealias Foo = TestDataClass2 7 | typealias Bar = TestDataClass2 8 | 9 | class IntArrayList : ArrayList() 10 | 11 | @JvmBuilder 12 | data class TestDataClass1(val counter: Int = 1, val name: List) { 13 | 14 | constructor() : this(0, listOf()) 15 | 16 | fun copy(): TestDataClass1 = TestDataClass1() 17 | fun copy(name: List): TestDataClass1 = TestDataClass1(counter = this.counter, name = name) 18 | } 19 | 20 | @JvmBuilder 21 | @Suppress("with") 22 | data class TestDataClass2(val generic1: T, val generic2: R) 23 | 24 | @JvmBuilder 25 | data class TestDataClass3(val foo: Foo, val bar: Bar?) 26 | 27 | @JvmBuilder 28 | data class TestDataClass4, R>(val a: TestDataClass2, val b: TestDataClass2) 29 | 30 | @JvmBuilder(prefix = "set") 31 | data class TestDataClass5(val foo: Foo, val list: IntArrayList?, val mList: MutableList?) 32 | 33 | @JvmBuilder 34 | data class TestDataClass6(val a: Int, val b: String = "foo") 35 | 36 | class Parent { 37 | @JvmBuilder 38 | data class TestDataClass6(val foo: Foo, val list: IntArrayList?) where R : Any, R : Runnable 39 | } 40 | 41 | @JvmBuilder 42 | data class Test(val foo: Int = 1, val bar: String) 43 | -------------------------------------------------------------------------------- /builder/sample/src/test/kotlin/TestDataClass1Test.java: -------------------------------------------------------------------------------- 1 | import com.example.JvmBuilder_TestDataClass1; 2 | import com.pholser.junit.quickcheck.Property; 3 | import com.pholser.junit.quickcheck.runner.JUnitQuickcheck; 4 | import java.util.List; 5 | import org.junit.runner.RunWith; 6 | 7 | @RunWith(JUnitQuickcheck.class) 8 | public class TestDataClass1Test { 9 | 10 | @Property 11 | public void name(List names, int counter) { 12 | new JvmBuilder_TestDataClass1().name(names).counter(counter).build(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /builder/sample/src/test/kotlin/TestDataClass1TestK.kt: -------------------------------------------------------------------------------- 1 | import com.example.JvmBuilder_TestDataClass1 2 | import com.pholser.junit.quickcheck.Property 3 | import com.pholser.junit.quickcheck.runner.JUnitQuickcheck 4 | import org.junit.jupiter.api.Assertions.assertEquals 5 | import org.junit.runner.RunWith 6 | 7 | @RunWith(JUnitQuickcheck::class) 8 | internal class TestDataClass1TestK { 9 | 10 | @Property 11 | internal fun `should create builder`(counter: Int, name: String?) { 12 | val (counter1, name1) = JvmBuilder_TestDataClass1().counter(counter).name(arrayListOf(name)).build() 13 | 14 | assertEquals(counter, counter1) 15 | assertEquals(arrayListOf(name), name1) 16 | } 17 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jffiorillo/jvmbuilder/34fdb7031c56c295dc92459d0d83952c9e16282d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jvmbuilder' 2 | include ':builder:sample' 3 | include ':builder:annotations' 4 | include ':builder:processor' 5 | include ':buildSrc' --------------------------------------------------------------------------------