├── .github ├── scripts │ └── publish.sh └── workflows │ ├── publish.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── ReadMe.md ├── annotations ├── build.gradle.kts ├── gradle.properties └── src │ └── commonMain │ └── kotlin │ └── com │ └── bennyhuo │ └── kotlin │ └── deepcopy │ └── annotations │ ├── DeepCopy.kt │ ├── DeepCopyConfig.kt │ └── DeepCopyIndex.kt ├── build.gradle ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── Versions.kt ├── compiler ├── compiler-apt │ ├── build.gradle.kts │ ├── gradle.properties │ └── src │ │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── bennyhuo │ │ │ └── kotlin │ │ │ └── deepcopy │ │ │ └── compiler │ │ │ └── apt │ │ │ ├── DeepCopyConfigIndex.kt │ │ │ ├── DeepCopyGenerator.kt │ │ │ ├── DeepCopyProcessor.kt │ │ │ ├── adapter │ │ │ ├── BaseAdapter.kt │ │ │ ├── CollectionAdapter.kt │ │ │ ├── DeepCopyAdapter.kt │ │ │ └── NoDeepCopyAdapter.kt │ │ │ ├── loop │ │ │ ├── DeepCopyLoopDetector.kt │ │ │ └── DeepCopyLoopException.kt │ │ │ ├── meta │ │ │ ├── KClassMeta.kt │ │ │ ├── KComponent.kt │ │ │ ├── KType.kt │ │ │ ├── KTypeElement.kt │ │ │ └── KTypeParameter.kt │ │ │ └── utils │ │ │ └── Utils.kt │ │ └── resources │ │ └── META-INF │ │ ├── gradle │ │ └── incremental.annotation.processors │ │ └── services │ │ └── javax.annotation.processing.Processor └── compiler-ksp │ ├── build.gradle.kts │ ├── gradle.properties │ └── src │ └── main │ ├── kotlin │ └── com │ │ └── bennyhuo │ │ └── kotlin │ │ └── deepcopy │ │ └── compiler │ │ └── ksp │ │ ├── DeepCopyConfigIndex.kt │ │ ├── DeepCopyGenerator.kt │ │ ├── DeepCopySymbolProcessor.kt │ │ ├── DeepCopySymbolProcessorProvider.kt │ │ ├── adapter │ │ ├── BaseAdapter.kt │ │ ├── CollectionAdapter.kt │ │ ├── DeepCopyAdapter.kt │ │ └── NoDeepCopyAdapter.kt │ │ ├── loop │ │ ├── DeepCopyLoopDetector.kt │ │ └── DeepCopyLoopException.kt │ │ ├── meta │ │ └── KComponent.kt │ │ └── utils │ │ ├── LoggerMixin.kt │ │ ├── Platform.kt │ │ └── Utils.kt │ └── resources │ └── META-INF │ └── services │ └── com.google.devtools.ksp.processing.SymbolProcessorProvider ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── install.sh ├── kcp-impl ├── build.gradle.kts ├── compiler-kcp-embeddable │ ├── build.gradle.kts │ └── gradle.properties ├── compiler-kcp │ ├── build.gradle.kts │ ├── gradle.properties │ ├── src │ │ ├── main │ │ │ └── kotlin │ │ │ │ └── com │ │ │ │ └── bennyhuo │ │ │ │ └── kotlin │ │ │ │ └── deepcopy │ │ │ │ └── compiler │ │ │ │ └── kcp │ │ │ │ ├── DeepCopyCommandLineProcessor.kt │ │ │ │ ├── DeepCopyComponentRegistrar.kt │ │ │ │ ├── PluginAvailability.kt │ │ │ │ ├── checker │ │ │ │ ├── DeepCopyComponentContainerContributor.kt │ │ │ │ ├── DeepCopyDeclarationChecker.kt │ │ │ │ ├── DefaultErrorMessagesDeepCopy.kt │ │ │ │ └── ErrorsDeepCopy.kt │ │ │ │ ├── ir │ │ │ │ ├── DeepCopyClassTransformer.kt │ │ │ │ ├── DeepCopyFunctionBuilder.kt │ │ │ │ └── DeepCopyIrGenerationExtension.kt │ │ │ │ ├── symbol │ │ │ │ ├── DeepCopyFunctionDescriptorImpl.kt │ │ │ │ └── DeepCopyResolveExtension.kt │ │ │ │ └── utils │ │ │ │ ├── KotlinType.kt │ │ │ │ └── Utils.kt │ │ └── test │ │ │ └── kotlin │ │ │ └── com │ │ │ └── bennyhuo │ │ │ └── kotlin │ │ │ └── kcp │ │ │ └── deepcopy │ │ │ └── compiler │ │ │ ├── DeepCopyTest.kt │ │ │ ├── IrDumpExtension.kt │ │ │ └── SimpleTest.kt │ └── testData │ │ ├── GenericsWithDeepCopyableBounds.kt │ │ ├── basic.kt │ │ ├── collectionElementCheck.kt │ │ ├── declaredDeepCopy.kt │ │ ├── deepCopyForInterface.kt │ │ ├── modules.kt │ │ └── simple.kt ├── gradle.properties ├── plugin-gradle │ ├── build.gradle.kts │ ├── gradle.properties │ └── src │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── bennyhuo │ │ └── kotlin │ │ └── deepcopy │ │ └── gradle │ │ └── DeepCopyGradlePlugin.kt ├── plugin-ide │ ├── build.gradle.kts │ ├── gradle.properties │ └── src │ │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── bennyhuo │ │ │ └── kotlin │ │ │ └── deepcopy │ │ │ └── ide │ │ │ ├── DeepCopyAvailability.kt │ │ │ ├── DeepCopyModelBuilder.kt │ │ │ ├── DeepCopyProjectResolverExtension.kt │ │ │ ├── GradleDeepCopyAvailabilityProvider.kt │ │ │ ├── IdeDeepCopyComponentContainerContributor.kt │ │ │ ├── IdeDeepCopyIrGenerationExtension.kt │ │ │ ├── IdeDeepCopyResolveExtension.kt │ │ │ ├── KotlinDeepCopyBundle.kt │ │ │ ├── Utils.kt │ │ │ └── quickfix │ │ │ ├── DeepCopyAddSupertypeQuickFix.kt │ │ │ ├── DeepCopyAnnotationQuickFix.kt │ │ │ ├── DeepCopyQuickFixContributor.kt │ │ │ └── DeepCopyQuickFixFactory.kt │ │ └── resources │ │ ├── META-INF │ │ ├── plugin.xml │ │ └── services │ │ │ └── org.jetbrains.plugins.gradle.tooling.ModelBuilderService │ │ └── messages │ │ └── KotlinDeepCopyBundle.properties └── sample │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ └── main │ └── java │ └── com │ └── bennyhuo │ └── kotlin │ └── deepcopy │ └── sample │ ├── Hello.java │ ├── QuickFix.kt │ └── Sample.kt ├── kotlin-js-store └── yarn.lock ├── reflect-impl-js ├── build.gradle.kts ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── bennyhuo │ │ └── kotlin │ │ └── deepcopy │ │ └── DeepCopy.kt │ └── test │ └── kotlin │ └── com │ └── bennyhuo │ └── kotlin │ └── deepcopy │ └── DeepCopyTest.kt ├── reflect-impl ├── build.gradle ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── bennyhuo │ │ └── kotlin │ │ └── deepcopy │ │ └── reflect │ │ └── DeepCopy.kt │ └── test │ └── kotlin │ └── com │ └── bennyhuo │ └── kotlin │ └── deepcopy │ └── compiler │ └── DeepCopyTest.kt ├── runtime ├── build.gradle ├── gradle.properties └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── bennyhuo │ │ └── kotlin │ │ └── deepcopy │ │ ├── CollectionSupport.kt │ │ ├── DeepCopyable.kt │ │ └── runtime │ │ └── DeepCopyableCollection.kt │ └── test │ └── kotlin │ └── com │ └── bennyhuo │ └── kotlin │ └── deepcopy │ └── runtime │ ├── BuiltinTypesTest.kt │ └── StarProjection.kt ├── sample ├── build.gradle ├── gradle.properties ├── library │ ├── build.gradle │ └── src │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── bennyhuo │ │ └── kotlin │ │ └── deepcopy │ │ └── library │ │ ├── PrebuiltClass.kt │ │ ├── innerclass │ │ └── InnerClasses.kt │ │ └── nullable │ │ └── Nullables.kt ├── sample-js │ ├── build.gradle.kts │ └── src │ │ └── jsMain │ │ └── kotlin │ │ └── Sample.kt └── sample-jvm │ ├── build.gradle │ └── src │ └── main │ └── kotlin │ └── com │ └── bennyhuo │ └── kotlin │ └── deepcopy │ └── sample │ ├── Sample.kt │ ├── collection │ └── Collections.kt │ ├── prebuilt │ ├── B.kt │ └── PrebuiltClass.kt │ ├── recursive │ ├── IssueCopyLoop.kt │ └── Recursive.kt │ └── typealias │ └── TypeAliases.kt ├── settings.gradle ├── test-common ├── build.gradle.kts ├── gradle.properties ├── src │ └── test │ │ └── kotlin │ │ └── com │ │ └── benyhuo │ │ └── kotlin │ │ └── deepcopy │ │ └── compiler │ │ ├── BaseTest.kt │ │ ├── KaptTest.kt │ │ ├── KspTest.kt │ │ ├── SimpleTest.kt │ │ └── TestBase.kt └── testData │ ├── Basic.kt │ ├── Collections.kt │ ├── GenericsWithDeepCopyableBounds.kt │ ├── InnerClasses.kt │ ├── Modules.kt │ ├── Nullables.kt │ ├── kapt │ ├── Config.kt │ ├── Generics.kt │ ├── Recursive.kt │ └── TypeAliases.kt │ └── ksp │ ├── Config.kt │ ├── Generics.kt │ ├── Recursive.kt │ └── TypeAliases.kt └── upload.sh /.github/scripts/publish.sh: -------------------------------------------------------------------------------- 1 | getProp(){ 2 | grep "${1}" gradle.properties | cut -d'=' -f2 | sed 's/\r//' 3 | } 4 | publishVersion=$(getProp VERSION_NAME) 5 | snapshotSuffix='SNAPSHOT' 6 | 7 | chmod +x ./gradlew 8 | ./gradlew publishAllPublicationsToMavenCentral 9 | if [[ "$publishVersion" != *"$snapshotSuffix"* ]]; then 10 | echo "auto release artifacts of ${publishVersion}" 11 | ./gradlew closeAndReleaseRepository 12 | fi 13 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | # Allows you to run this workflow manually from the Actions tab 9 | workflow_dispatch: 10 | 11 | jobs: 12 | publish: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Check out 16 | uses: actions/checkout@v2 17 | 18 | - name: Set up JDK 11 19 | uses: actions/setup-java@v2 20 | with: 21 | distribution: 'zulu' 22 | java-version: '11' 23 | 24 | - name: Build & Publish to Maven Central 25 | run: chmod +x .github/scripts/publish.sh && .github/scripts/publish.sh 26 | env: 27 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} 28 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} 29 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }} 30 | ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_IN_MEMORY_KEY_ID }} 31 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }} 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Tagged Release" 3 | 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | 9 | jobs: 10 | tagged-release: 11 | name: "Tagged Release" 12 | runs-on: "ubuntu-latest" 13 | 14 | steps: 15 | - uses: "marvinpinto/action-automatic-releases@v1.2.1" 16 | with: 17 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 18 | prerelease: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build 4 | out 5 | local.properties 6 | jcenter.properties 7 | 8 | # Compiled class file 9 | *.class 10 | 11 | # Log file 12 | *.log 13 | 14 | # BlueJ files 15 | *.ctxt 16 | 17 | # Mobile Tools for Java (J2ME) 18 | .mtj.tmp/ 19 | 20 | # Package Files # 21 | *.jar 22 | *.war 23 | *.nar 24 | *.ear 25 | *.zip 26 | *.tar.gz 27 | *.rar 28 | 29 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 30 | hs_err_pid* 31 | 32 | composite_build.local -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bennyhuo 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 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.bennyhuo.kotlin/deepcopy-reflect/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.bennyhuo.kotlin/deepcopy-reflect) 2 | 3 | 4 | # KotlinDeepCopy 5 | 6 | Provide an easy way to generate `DeepCopy` function for `data class`. DeepCopy only takes effect on the component members i.e. the members declared in the primary constructor. 7 | 8 | ## Reflection 9 | 10 | Use Kotlin Reflection to provide an extension function for `DeepCopyable` so that any data class which implements `DeepCopyable` can simply call `deepCopy()` to copy itsself. 11 | 12 | See the test code below: 13 | 14 | ```kotlin 15 | data class Speaker(val name: String, val age: Int): DeepCopyable 16 | 17 | data class Talk(val name: String, val speaker: Speaker): DeepCopyable 18 | 19 | class DeepCopyTest { 20 | @Test 21 | fun test() { 22 | val talk = Talk("DataClass in Action", Speaker("Benny Huo", 30)) 23 | val newTalk = talk.deepCopy() 24 | assert(talk == newTalk) 25 | assert(talk !== newTalk) 26 | } 27 | } 28 | ``` 29 | 30 | `talk` equals `newTalk` since the values of their members are equal while they do not ref to the same object. 31 | 32 | ### How to Setup 33 | 34 | This library has been deloyed to maven center. 35 | 36 | ```gradle 37 | implementation("com.bennyhuo.kotlin:deepcopy-reflect:") 38 | ``` 39 | 40 | ## Apt & Ksp 41 | 42 | If you concern about the runtime efficiency, apt may be the solution. You can simply annotate the data class you want to deep copy with a `DeepCopy` annotation: 43 | 44 | ```kotlin 45 | @DeepCopy 46 | data class Speaker(val name: String, val age: Int) 47 | @DeepCopy 48 | data class Talk(val name: String, val speaker: Speaker) 49 | ``` 50 | 51 | Extension function `deepCopy` will be generated according to the components: 52 | 53 | ```kotlin 54 | fun Talk.deepCopy(name: String = this.name, speaker: Speaker = this.speaker): Talk = Talk(name, speaker.deepCopy()) 55 | 56 | fun Speaker.deepCopy( 57 | name: String = this.name, 58 | age: Int = this.age, 59 | company: Company = this.company 60 | ): Speaker = Speaker(name, age, company.deepCopy()) 61 | ``` 62 | 63 | Notice that `deepCopy` is called recursively if the member type is also a `data class` annotated with a `DeepCopy` annotation. Hence, if you remove the annotation for `Speaker`, generated function would be like: 64 | 65 | ```kotlin 66 | fun Talk.deepCopy(name: String = this.name, speaker: Speaker = this.speaker): Talk = Talk(name, speaker) 67 | 68 | //And no deepCopy for Speaker. 69 | ``` 70 | 71 | ### How to Setup 72 | 73 | The artifacts have been deployed to maven central repository. Set up your project by adding these lines: 74 | 75 | ```gradle 76 | plugins { 77 | id("com.google.devtools.ksp") version "1.9.20-1.0.14" // ksp 78 | id "org.jetbrains.kotlin.kapt" // kapt 79 | } 80 | ... 81 | 82 | dependencies { 83 | ksp("com.bennyhuo.kotlin:deepcopy-compiler-ksp:")) // ksp 84 | kapt("com.bennyhuo.kotlin:deepcopy-compiler-kapt:") // kapt 85 | implementation("com.bennyhuo.kotlin:deepcopy-runtime:") 86 | } 87 | ``` 88 | 89 | ## KCP 90 | 91 | This is a nearly perfect version I think. It works like `copy` does. You can install this IntelliJ plugin: [DeepCopy](https://plugins.jetbrains.com/plugin/19915-deepcopy-for-kotlin-data-class) and setup your project like this: 92 | 93 | ```gradle 94 | plugins { 95 | kotlin("jvm") version "1.9.20" 96 | id("com.bennyhuo.kotlin.plugin.deepcopy") version "" 97 | } 98 | 99 | dependencies { 100 | implementation("com.bennyhuo.kotlin:deepcopy-runtime:") 101 | } 102 | ``` 103 | 104 | And then try to call the `deepCopy` function directly! 105 | 106 | # Change Log 107 | 108 | See [releases](https://github.com/bennyhuo/KotlinDeepCopy/releases). 109 | 110 | # License 111 | 112 | [MIT License](LICENSE) 113 | 114 | Copyright (c) 2018 Bennyhuo 115 | 116 | Permission is hereby granted, free of charge, to any person obtaining a copy 117 | of this software and associated documentation files (the "Software"), to deal 118 | in the Software without restriction, including without limitation the rights 119 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 120 | copies of the Software, and to permit persons to whom the Software is 121 | furnished to do so, subject to the following conditions: 122 | 123 | The above copyright notice and this permission notice shall be included in all 124 | copies or substantial portions of the Software. 125 | 126 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 127 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 128 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 129 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 130 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 131 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 132 | SOFTWARE. 133 | 134 | 135 | -------------------------------------------------------------------------------- /annotations/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension 2 | import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin 3 | 4 | plugins { 5 | kotlin("multiplatform") 6 | } 7 | 8 | kotlin { 9 | jvm() 10 | js(IR) { 11 | nodejs {} 12 | } 13 | sourceSets { 14 | val commonMain by getting { 15 | dependencies { 16 | api("com.bennyhuo.kotlin:annotations-module-support:$moduleSupportVersion") 17 | } 18 | } 19 | } 20 | } 21 | 22 | rootProject.plugins.withType { 23 | rootProject.the().nodeVersion = "16.0.0" 24 | } 25 | -------------------------------------------------------------------------------- /annotations/gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=com.bennyhuo.kotlin 2 | 3 | POM_ARTIFACT_ID=deepcopy-annotations 4 | POM_NAME=KotlinDeepCopy-Annotations -------------------------------------------------------------------------------- /annotations/src/commonMain/kotlin/com/bennyhuo/kotlin/deepcopy/annotations/DeepCopy.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.annotations 2 | 3 | @Target(AnnotationTarget.CLASS) 4 | @Retention(AnnotationRetention.BINARY) 5 | annotation class DeepCopy -------------------------------------------------------------------------------- /annotations/src/commonMain/kotlin/com/bennyhuo/kotlin/deepcopy/annotations/DeepCopyConfig.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.annotations 2 | 3 | import kotlin.reflect.KClass 4 | 5 | annotation class DeepCopyConfig(val values: Array> = emptyArray()) -------------------------------------------------------------------------------- /annotations/src/commonMain/kotlin/com/bennyhuo/kotlin/deepcopy/annotations/DeepCopyIndex.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.annotations 2 | 3 | annotation class DeepCopyIndex(val values: Array = emptyArray()) -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | kotlin_version = "1.9.20" 4 | ksp_version = "1.9.20-1.0.14" 5 | } 6 | } 7 | 8 | plugins { 9 | id("org.jetbrains.kotlin.jvm") version "1.9.20" apply false 10 | id("org.jetbrains.dokka") version "1.7.10" apply false 11 | id("com.github.gmazzo.buildconfig") version "2.1.0" apply false 12 | id("com.vanniktech.maven.publish") version "0.25.2" apply false 13 | id("com.bennyhuo.kotlin.plugin.embeddable") version "1.8.0.0" apply false 14 | id("com.bennyhuo.kotlin.plugin.embeddable.test") version "1.8.0.0" apply false 15 | } 16 | 17 | subprojects { project -> 18 | if(!project.hasProperty("shouldPublish") || project.property("shouldPublish") == "true") { 19 | group = GROUP 20 | version = VERSION_NAME 21 | 22 | project.apply plugin: "com.vanniktech.maven.publish" 23 | } 24 | 25 | project.pluginManager.withPlugin("java") { 26 | project.sourceCompatibility = 1.8 27 | } 28 | 29 | repositories { 30 | mavenCentral() 31 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | gradlePluginPortal() 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | implementation("com.github.jengelman.gradle.plugins:shadow:6.1.0") 12 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Versions.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by benny. 3 | */ 4 | const val kotlinVersion = "1.9.20" 5 | const val kspVersion = "1.9.20-1.0.14" 6 | const val kotlinPoetVersion = "1.12.0" 7 | const val moduleSupportVersion = "1.9.20-1.0.0" 8 | const val compileTestingExtensionsVersion = "1.9.20-1.3.0" 9 | -------------------------------------------------------------------------------- /compiler/compiler-apt/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | java 5 | kotlin("jvm") 6 | } 7 | 8 | dependencies { 9 | api("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") 10 | api("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.7.0") 11 | api("com.bennyhuo:aptutils:1.8") 12 | api("com.squareup:kotlinpoet:1.10.2") 13 | api("com.squareup:kotlinpoet-metadata:1.10.2") 14 | 15 | api(project(":annotations")) 16 | 17 | api("com.bennyhuo.kotlin:apt-module-support:$moduleSupportVersion") 18 | } 19 | 20 | tasks.withType().configureEach { 21 | kotlinOptions { 22 | freeCompilerArgs = listOf( 23 | "-Xuse-experimental=com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview", 24 | "-Xopt-in=kotlin.contracts.ExperimentalContracts" 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /compiler/compiler-apt/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=deepcopy-compiler-kapt 2 | POM_NAME=KotlinDeepCopy-Compiler-Kapt -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/DeepCopyConfigIndex.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt 2 | 3 | import com.bennyhuo.aptutils.types.asElement 4 | import com.bennyhuo.aptutils.types.asTypeMirror 5 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 6 | import javax.lang.model.element.Element 7 | import javax.lang.model.element.TypeElement 8 | import javax.lang.model.type.MirroredTypesException 9 | 10 | class DeepCopyConfigIndex(configs: Collection?, libraryConfigs: Collection?) { 11 | 12 | companion object { 13 | val instance: DeepCopyConfigIndex 14 | get() = currentInstance!! 15 | 16 | private var currentInstance: DeepCopyConfigIndex? = null 17 | 18 | operator fun contains(element: TypeElement): Boolean { 19 | return element.qualifiedName.toString() in instance.allDeepCopyClassNamesFromConfigs 20 | } 21 | 22 | fun release() { 23 | currentInstance = null 24 | } 25 | } 26 | 27 | init { 28 | currentInstance = this 29 | } 30 | 31 | val deepCopyClassesFromConfig = parseConfigs(configs) 32 | 33 | val deepCopyClassesFromLibraryConfigs = parseConfigs(libraryConfigs) 34 | 35 | private val allDeepCopyClassNamesFromConfigs = (deepCopyClassesFromConfig + deepCopyClassesFromLibraryConfigs).mapTo(HashSet()) { 36 | it.qualifiedName.toString() 37 | } 38 | 39 | private fun parseConfigs(configs: Collection?): Set { 40 | return configs?.asSequence()?.map { 41 | it.getAnnotation(DeepCopyConfig::class.java) 42 | }?.flatMap { 43 | try { 44 | it.values.map { it.asTypeMirror() } 45 | } catch (e: MirroredTypesException) { 46 | e.typeMirrors 47 | } 48 | }?.map { it.asElement() } 49 | ?.filterIsInstance() 50 | ?.toSet() ?: emptySet() 51 | } 52 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/DeepCopyGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt 2 | 3 | import com.bennyhuo.aptutils.AptContext 4 | import com.bennyhuo.aptutils.types.packageName 5 | import com.bennyhuo.aptutils.types.simpleName 6 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.adapter.Adapter 7 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.meta.KTypeElement 8 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.utils.escapeStdlibPackageName 9 | import com.squareup.kotlinpoet.AnnotationSpec 10 | import com.squareup.kotlinpoet.FileSpec 11 | import com.squareup.kotlinpoet.FunSpec 12 | import com.squareup.kotlinpoet.KModifier 13 | import com.squareup.kotlinpoet.ParameterSpec 14 | 15 | class DeepCopyGenerator(val kTypeElement: KTypeElement){ 16 | 17 | companion object { 18 | const val POSIX = "$\$DeepCopy" 19 | } 20 | 21 | fun generate(){ 22 | val fileSpecBuilder = FileSpec.builder( 23 | escapeStdlibPackageName(kTypeElement.packageName()), 24 | kTypeElement.simpleName() + POSIX 25 | ) 26 | val functionBuilder = FunSpec.builder("deepCopy") 27 | .receiver(kTypeElement.kotlinClassName) 28 | .addModifiers(KModifier.PUBLIC) 29 | .returns(kTypeElement.kotlinClassName) 30 | .addOriginatingElement(kTypeElement.typeElement) 31 | 32 | functionBuilder.addTypeVariables(kTypeElement.typeVariablesWithoutVariance) 33 | .addAnnotation(JvmOverloads::class) 34 | 35 | val suppressWarnings = hashSetOf() 36 | 37 | val statementStringBuilder = StringBuilder("%T(") 38 | 39 | kTypeElement.components.forEach { component -> 40 | val adapter = Adapter(component) 41 | adapter.addImport(fileSpecBuilder) 42 | adapter.addStatement(statementStringBuilder) 43 | 44 | functionBuilder.addParameter( 45 | ParameterSpec.builder(component.name, component.type) 46 | .defaultValue("this.${component.name}").build() 47 | ) 48 | } 49 | statementStringBuilder.setCharAt(statementStringBuilder.lastIndex - 1, ')') 50 | 51 | functionBuilder.addStatement("return $statementStringBuilder", 52 | kTypeElement.kotlinClassName) 53 | 54 | suppressWarnings.forEach { 55 | functionBuilder.addAnnotation(AnnotationSpec.builder(Suppress::class).addMember("%S", it).build()) 56 | } 57 | 58 | fileSpecBuilder.addFunction(functionBuilder.build()).build().writeTo(AptContext.filer) 59 | } 60 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/DeepCopyProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt 2 | 3 | import com.bennyhuo.aptutils.AptContext 4 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 5 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 6 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.loop.DeepCopyLoopDetector 7 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.loop.DeepCopyLoopException 8 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.meta.KTypeElement 9 | import com.bennyhuo.kotlin.processor.module.apt.AptModuleProcessor 10 | import com.bennyhuo.kotlin.processor.module.common.MODULE_MIXED 11 | import javax.annotation.processing.ProcessingEnvironment 12 | import javax.annotation.processing.RoundEnvironment 13 | import javax.annotation.processing.SupportedAnnotationTypes 14 | import javax.annotation.processing.SupportedSourceVersion 15 | import javax.lang.model.SourceVersion 16 | import javax.lang.model.element.Element 17 | import javax.lang.model.element.TypeElement 18 | import javax.tools.Diagnostic 19 | 20 | @SupportedAnnotationTypes( 21 | "com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy", 22 | "com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig" 23 | ) 24 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 25 | class DeepCopyProcessor : AptModuleProcessor() { 26 | override val annotationsForIndex = setOf(DeepCopyConfig::class.java.name) 27 | 28 | override val processorName: String = "deepCopy" 29 | 30 | override val supportedModuleTypes: Set = setOf(MODULE_MIXED) 31 | 32 | override fun init(processingEnv: ProcessingEnvironment) { 33 | super.init(processingEnv) 34 | AptContext.init(processingEnv) 35 | } 36 | 37 | override fun processMain( 38 | roundEnv: RoundEnvironment, 39 | annotatedSymbols: Map>, 40 | annotatedSymbolsFromLibrary: Map>, 41 | ) { 42 | val configIndex = DeepCopyConfigIndex( 43 | annotatedSymbols[DeepCopyConfig::class.java.name], 44 | annotatedSymbolsFromLibrary[DeepCopyConfig::class.java.name], 45 | ) 46 | 47 | try { 48 | roundEnv.getElementsAnnotatedWith(DeepCopy::class.java) 49 | .filterIsInstance() 50 | .filter { it.kind.isClass } 51 | .plus(configIndex.deepCopyClassesFromConfig) 52 | .map { 53 | KTypeElement.from(it) 54 | }.forEach { 55 | DeepCopyLoopDetector(it).detect() 56 | DeepCopyGenerator(it).generate() 57 | } 58 | } catch (e: DeepCopyLoopException) { 59 | AptContext.messager.printMessage(Diagnostic.Kind.ERROR, e.message, e.element) 60 | } finally { 61 | DeepCopyConfigIndex.release() 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/adapter/BaseAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt.adapter 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.meta.KComponent 4 | import com.squareup.kotlinpoet.FileSpec 5 | 6 | /** 7 | * Created by benny. 8 | */ 9 | abstract class BaseAdapter( 10 | val component: KComponent 11 | ) { 12 | protected val nullableMark = if (component.type.isNullable) "?" else "" 13 | 14 | abstract fun addImport(builder: FileSpec.Builder) 15 | 16 | abstract fun addStatement(builder: StringBuilder) 17 | 18 | } 19 | 20 | fun Adapter(component: KComponent): BaseAdapter { 21 | return when { 22 | component.isDeepCopyable -> { 23 | DeepCopyAdapter(component) 24 | } 25 | component.typeElement?.isCollectionType == true -> { 26 | DeepCopyableCollectionAdapter(component) 27 | } 28 | component.typeElement?.isMapType == true -> { 29 | DeepCopyableMapAdapter(component) 30 | } 31 | else -> { 32 | NoDeepCopyAdapter(component) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/adapter/CollectionAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt.adapter 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.meta.KComponent 4 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.utils.RUNTIME_PACKAGE 5 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.utils.escapedPackageName 6 | import com.squareup.kotlinpoet.FileSpec 7 | 8 | /** 9 | * Created by benny. 10 | */ 11 | abstract class BaseCollectionAdapter(component: KComponent) : BaseAdapter(component) { 12 | 13 | 14 | override fun addImport(builder: FileSpec.Builder) { 15 | builder.addImport(RUNTIME_PACKAGE, "deepCopy") 16 | } 17 | 18 | override fun addStatement(builder: StringBuilder) { 19 | builder.append("${component.name}${nullableMark}.${deepCopyCall()}, ") 20 | } 21 | 22 | abstract fun deepCopyCall(): String 23 | 24 | } 25 | 26 | class DeepCopyableCollectionAdapter(component: KComponent) : BaseCollectionAdapter(component) { 27 | 28 | override fun addImport(builder: FileSpec.Builder) { 29 | super.addImport(builder) 30 | 31 | val elementType = component.typeArgumentElements.single() 32 | if (elementType?.isDeepCopyable == true) { 33 | builder.addImport(elementType.escapedPackageName, "deepCopy") 34 | } 35 | } 36 | 37 | override fun deepCopyCall(): String { 38 | return "deepCopy${ 39 | if (component.isTypeArgumentDeepCopyable(0)) " { it.deepCopy() }" 40 | else "()" 41 | }" 42 | } 43 | } 44 | 45 | class DeepCopyableMapAdapter(component: KComponent) : BaseCollectionAdapter(component) { 46 | private val bodyForDeepCopyable = "{ it.deepCopy() }" 47 | private val bodyForNonDeepCopyable = "{ it }" 48 | 49 | override fun addImport(builder: FileSpec.Builder) { 50 | super.addImport(builder) 51 | 52 | val keyType = component.typeArgumentElements[0] 53 | if (keyType?.isDeepCopyable == true) { 54 | builder.addImport(keyType.escapedPackageName, "deepCopy") 55 | } 56 | 57 | val valueType = component.typeArgumentElements[1] 58 | if (valueType?.isDeepCopyable == true) { 59 | builder.addImport(valueType.escapedPackageName, "deepCopy") 60 | } 61 | } 62 | 63 | override fun deepCopyCall(): String { 64 | return "deepCopy(${ 65 | (0..1).joinToString { 66 | if (component.isTypeArgumentDeepCopyable(it)) bodyForDeepCopyable 67 | else bodyForNonDeepCopyable 68 | } 69 | })" 70 | } 71 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/adapter/DeepCopyAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt.adapter 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.meta.KComponent 4 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.utils.escapedPackageName 5 | import com.squareup.kotlinpoet.FileSpec 6 | 7 | /** 8 | * Created by benny. 9 | */ 10 | class DeepCopyAdapter(component: KComponent) : BaseAdapter(component) { 11 | override fun addImport(builder: FileSpec.Builder) { 12 | if (component.isDeepCopyableClass) { 13 | builder.addImport(component.typeElement!!.escapedPackageName, "deepCopy") 14 | } 15 | } 16 | 17 | override fun addStatement(builder: StringBuilder) { 18 | builder.append("${component.name}${nullableMark}.deepCopy(), ") 19 | } 20 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/adapter/NoDeepCopyAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt.adapter 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.meta.KComponent 4 | import com.squareup.kotlinpoet.FileSpec 5 | 6 | /** 7 | * Created by benny. 8 | */ 9 | class NoDeepCopyAdapter( 10 | component: KComponent 11 | ): BaseAdapter(component){ 12 | override fun addImport(builder: FileSpec.Builder) = Unit 13 | 14 | override fun addStatement(builder: StringBuilder) { 15 | builder.append("${component.name}, ") 16 | } 17 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/loop/DeepCopyLoopDetector.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt.loop 2 | 3 | import com.bennyhuo.aptutils.logger.Logger 4 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.meta.KTypeElement 5 | import java.util.* 6 | 7 | class DeepCopyLoopDetector(private val kTypeElement: KTypeElement) { 8 | 9 | private val typeStack = Stack() 10 | 11 | fun detect() { 12 | push(kTypeElement) 13 | kTypeElement.components 14 | // Only nullable types should be checked. 15 | .filter { it.type.isNullable } 16 | .mapNotNull { it.typeElement } 17 | .filter { it.isDeepCopyable } 18 | .forEach { 19 | push(it) 20 | detectNext(it) 21 | pop() 22 | } 23 | pop() 24 | } 25 | 26 | private fun detectNext(kTypeElement: KTypeElement) { 27 | kTypeElement.components.mapNotNull { it.typeElement } 28 | .filter { it.isDeepCopyable } 29 | .forEach { 30 | push(it) 31 | detectNext(it) 32 | pop() 33 | } 34 | } 35 | 36 | private fun push(kTypeElement: KTypeElement) { 37 | kTypeElement.mark() 38 | typeStack.push(kTypeElement) 39 | } 40 | 41 | private fun pop() { 42 | typeStack.pop()?.unmark() 43 | } 44 | 45 | private fun dumpStack() { 46 | Logger.warn("${kTypeElement.qualifiedName}: [${typeStack.joinToString { it.simpleName }}]") 47 | } 48 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/loop/DeepCopyLoopException.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt.loop 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.meta.KTypeElement 4 | 5 | class DeepCopyLoopException(kTypeElement: KTypeElement) : 6 | Exception("Detect infinite copy loop. It will cause stack overflow to call ${kTypeElement.kotlinClassName}.deepCopy() in the runtime.") { 7 | 8 | val element = kTypeElement.typeElement 9 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/meta/KClassMeta.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt.meta 2 | 3 | import kotlinx.metadata.ClassName 4 | import kotlinx.metadata.Flag 5 | import kotlinx.metadata.Flags 6 | import kotlinx.metadata.KmClassVisitor 7 | import kotlinx.metadata.KmConstructorVisitor 8 | import kotlinx.metadata.KmTypeParameterVisitor 9 | import kotlinx.metadata.KmTypeVisitor 10 | import kotlinx.metadata.KmValueParameterVisitor 11 | import kotlinx.metadata.KmVariance 12 | import kotlinx.metadata.jvm.KotlinClassMetadata 13 | 14 | class KClassMeta(kotlinClassMetadata: KotlinClassMetadata.Class) { 15 | 16 | var isData: Boolean = false 17 | private set 18 | 19 | val components = mutableListOf() 20 | 21 | val typeParameters = mutableListOf() 22 | 23 | init { 24 | kotlinClassMetadata.accept(object : KmClassVisitor() { 25 | override fun visit(flags: Flags, name: ClassName) { 26 | super.visit(flags, name) 27 | isData = Flag.Class.IS_DATA(flags) 28 | } 29 | 30 | override fun visitTypeParameter( 31 | flags: Flags, 32 | name: String, 33 | id: Int, 34 | variance: KmVariance 35 | ): KmTypeParameterVisitor { 36 | return KTypeParameter(flags, name, id, variance) { 37 | KType(it, typeParameters) 38 | }.also { 39 | typeParameters += it 40 | } 41 | } 42 | 43 | override fun visitConstructor(flags: Flags): KmConstructorVisitor? { 44 | if (!Flag.Constructor.IS_SECONDARY(flags)) { 45 | return object : KmConstructorVisitor() { 46 | override fun visitValueParameter( 47 | flags: Flags, 48 | name: String 49 | ): KmValueParameterVisitor { 50 | return object : KmValueParameterVisitor() { 51 | override fun visitType(flags: Flags): KmTypeVisitor { 52 | return object: KType(flags, typeParameters){ 53 | override fun visitEnd() { 54 | super.visitEnd() 55 | components += KComponent(name, type) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | return super.visitConstructor(flags) 64 | } 65 | }) 66 | } 67 | 68 | override fun toString(): String { 69 | return "isData=$isData, components=${components.joinToString()}" 70 | } 71 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/meta/KComponent.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt.meta 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.utils.isDeepCopyTypeVariable 4 | import com.squareup.kotlinpoet.ParameterizedTypeName 5 | import com.squareup.kotlinpoet.TypeName 6 | 7 | data class KComponent(val name: String, val type: TypeName) { 8 | 9 | val typeElement: KTypeElement? by lazy { 10 | KTypeElement.from(type) 11 | } 12 | 13 | val typeArgumentElements: List by lazy { 14 | if (type is ParameterizedTypeName) { 15 | type.typeArguments.map { 16 | KTypeElement.from(it) 17 | } 18 | } else { 19 | emptyList() 20 | } 21 | } 22 | 23 | val typeArguments: List by lazy { 24 | if (type is ParameterizedTypeName) { 25 | type.typeArguments 26 | } else { 27 | emptyList() 28 | } 29 | } 30 | 31 | val isDeepCopyableClass: Boolean by lazy { 32 | typeElement.isDeepCopyable() 33 | } 34 | 35 | val isDeepCopyableTypeVariable: Boolean by lazy { 36 | type.isDeepCopyTypeVariable() 37 | } 38 | 39 | val isDeepCopyable: Boolean by lazy { 40 | isDeepCopyableClass || isDeepCopyableTypeVariable 41 | } 42 | 43 | fun isTypeArgumentDeepCopyable(index: Int): Boolean { 44 | return if (type is ParameterizedTypeName) { 45 | type.typeArguments[index].let { 46 | KTypeElement.from(it).isDeepCopyable() || it.isDeepCopyTypeVariable() 47 | } 48 | } else { 49 | false 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/meta/KTypeElement.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt.meta 2 | 3 | import com.bennyhuo.aptutils.AptContext 4 | import com.bennyhuo.aptutils.types.asKotlinTypeName 5 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 6 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.DeepCopyConfigIndex 7 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.loop.DeepCopyLoopException 8 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.utils.isSupportedCollectionType 9 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.utils.isSupportedMapType 10 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.utils.kotlinCollectionTypeToJvmType 11 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.utils.parse 12 | import com.squareup.kotlinpoet.ClassName 13 | import com.squareup.kotlinpoet.ParameterizedTypeName 14 | import com.squareup.kotlinpoet.TypeName 15 | import com.squareup.kotlinpoet.TypeVariableName 16 | import com.squareup.kotlinpoet.WildcardTypeName 17 | import kotlinx.metadata.jvm.KotlinClassMetadata 18 | import java.util.* 19 | import javax.lang.model.element.TypeElement 20 | import kotlin.contracts.contract 21 | 22 | class KTypeElement private constructor( 23 | val typeElement: TypeElement, 24 | val kotlinClassName: TypeName 25 | ) : TypeElement by typeElement { 26 | 27 | companion object { 28 | private val refs = WeakHashMap() 29 | 30 | fun from(typeName: TypeName): KTypeElement? { 31 | val className = when (typeName) { 32 | is ParameterizedTypeName -> typeName.rawType.canonicalName 33 | is ClassName -> typeName.canonicalName 34 | is TypeVariableName, 35 | is WildcardTypeName -> return null 36 | else -> throw IllegalArgumentException("Illegal type: $typeName") 37 | } 38 | val mappedCollectionName = kotlinCollectionTypeToJvmType[className] 39 | val name = mappedCollectionName ?: className 40 | return refs[typeName.toString()] ?: AptContext.elements.getTypeElement(name)?.let { 41 | KTypeElement(it, typeName) 42 | }?.also { refs[typeName.toString()] = it } 43 | } 44 | 45 | fun from(typeElement: TypeElement): KTypeElement { 46 | val className = typeElement.qualifiedName.toString() 47 | return refs[className] ?: KTypeElement( 48 | typeElement, 49 | typeElement.asType().asKotlinTypeName() 50 | ).also { refs[className] = it } 51 | } 52 | } 53 | 54 | private val kClassMeta: KClassMeta? = getAnnotation(Metadata::class.java)?.let { 55 | it.parse() as? KotlinClassMetadata.Class 56 | }?.let(::KClassMeta) 57 | 58 | val isDataClass = kClassMeta?.isData ?: false 59 | 60 | val isCollectionType by lazy { 61 | typeElement.isSupportedCollectionType 62 | } 63 | 64 | val isMapType by lazy { 65 | typeElement.isSupportedMapType 66 | } 67 | 68 | val isDeepCopyable = isDataClass && (typeElement.getAnnotation(DeepCopy::class.java) != null 69 | || typeElement in DeepCopyConfigIndex) 70 | 71 | val components = kClassMeta?.components ?: emptyList() 72 | 73 | val typeVariablesWithoutVariance = kClassMeta?.typeParameters?.map { 74 | it.typeVariableNameWithoutVariance 75 | } ?: emptyList() 76 | 77 | val typeVariables = kClassMeta?.typeParameters?.map { 78 | it.typeVariableName 79 | } ?: emptyList() 80 | 81 | private var marked: Boolean = false 82 | 83 | fun mark() { 84 | if (marked) { 85 | throw DeepCopyLoopException(this) 86 | } else { 87 | marked = true 88 | } 89 | } 90 | 91 | fun unmark() { 92 | marked = false 93 | } 94 | 95 | override fun toString(): String { 96 | return kotlinClassName.toString() 97 | } 98 | } 99 | 100 | fun KTypeElement?.isDeepCopyable(): Boolean { 101 | contract { 102 | returns(true) implies (this@isDeepCopyable != null) 103 | } 104 | return this?.isDeepCopyable == true 105 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/meta/KTypeParameter.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt.meta 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | import com.squareup.kotlinpoet.TypeVariableName 5 | import kotlinx.metadata.Flags 6 | import kotlinx.metadata.KmTypeParameterVisitor 7 | import kotlinx.metadata.KmTypeVisitor 8 | import kotlinx.metadata.KmVariance 9 | 10 | class KTypeParameter( 11 | val flags: Flags, 12 | val name: String, 13 | val id: Int, 14 | val variance: KmVariance, 15 | val kTypeCreator: (flags: Flags) -> KType 16 | ) : KmTypeParameterVisitor() { 17 | 18 | val upperBounds = ArrayList() 19 | 20 | val typeVariableNameWithoutVariance by lazy { 21 | if (upperBounds.isEmpty()) { 22 | TypeVariableName(name) 23 | } else { 24 | TypeVariableName(name, upperBounds.map { it.type }) 25 | } 26 | } 27 | 28 | val typeVariableName by lazy { 29 | val variance = when(variance){ 30 | KmVariance.INVARIANT -> null 31 | KmVariance.IN -> KModifier.IN 32 | KmVariance.OUT -> KModifier.OUT 33 | } 34 | 35 | if (upperBounds.isEmpty()) { 36 | TypeVariableName(name, variance) 37 | } else { 38 | TypeVariableName(name, upperBounds.map { it.type }, variance) 39 | } 40 | } 41 | 42 | override fun visitUpperBound(flags: Flags): KmTypeVisitor { 43 | return kTypeCreator(flags).also { upperBounds += it } 44 | } 45 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/apt/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.apt.utils 2 | 3 | import com.bennyhuo.aptutils.types.ClassType 4 | import com.bennyhuo.aptutils.types.packageName 5 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.meta.KTypeElement 6 | import com.squareup.kotlinpoet.ClassName 7 | import com.squareup.kotlinpoet.ParameterizedTypeName 8 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 9 | import com.squareup.kotlinpoet.TypeName 10 | import com.squareup.kotlinpoet.TypeVariableName 11 | import kotlinx.metadata.jvm.KotlinClassHeader 12 | import kotlinx.metadata.jvm.KotlinClassMetadata 13 | import javax.lang.model.element.TypeElement 14 | import kotlin.contracts.contract 15 | 16 | fun Metadata.parse() = KotlinClassMetadata.read( 17 | KotlinClassHeader( 18 | this.kind, 19 | this.metadataVersion, 20 | this.data1, this.data2, this.extraString, this.packageName, this.extraInt 21 | ) 22 | ) 23 | 24 | val kotlinCollectionTypeToJvmType = mapOf( 25 | //builtins 26 | "kotlin.collections.Collection" to "java.util.Collection", 27 | "kotlin.collections.MutableCollection" to "java.util.Collection", 28 | "kotlin.collections.List" to "java.util.List", 29 | "kotlin.collections.MutableList" to "java.util.List", 30 | "kotlin.collections.Set" to "java.util.Set", 31 | "kotlin.collections.MutableSet" to "java.util.Set", 32 | "kotlin.collections.Map" to "java.util.Map", 33 | "kotlin.collections.MutableMap" to "java.util.Map", 34 | 35 | //typealiases 36 | "kotlin.collections.ArrayList" to "java.util.ArrayList", 37 | "kotlin.collections.LinkedHashSet" to "java.util.LinkedHashSet", 38 | "kotlin.collections.HashSet" to "java.util.HashSet", 39 | "kotlin.collections.LinkedHashMap" to "java.util.LinkedHashMap", 40 | "kotlin.collections.HashMap" to "java.util.HashMap" 41 | ) 42 | 43 | fun mapKotlinCollectionTypeToJvmType(type: ParameterizedTypeName): ParameterizedTypeName { 44 | val mappedType = when (type.rawType.canonicalName) { 45 | "kotlin.collections.Collection", 46 | "kotlin.collections.MutableCollection" -> ClassType("java.util.Collection") 47 | 48 | "kotlin.collections.List", 49 | "kotlin.collections.MutableList" -> ClassType("java.util.List") 50 | 51 | "kotlin.collections.Set", 52 | "kotlin.collections.MutableSet" -> ClassType("java.util.Set") 53 | 54 | "kotlin.collections.Map", 55 | "kotlin.collections.MutableMap" -> ClassType("java.util.Map") 56 | 57 | else -> null 58 | } 59 | return (mappedType?.kotlin as? ClassName)?.parameterizedBy(*type.typeArguments.toTypedArray()) 60 | ?: type 61 | } 62 | 63 | inline fun escapeStdlibPackageName(packageName: String) = 64 | if (packageName == "kotlin") "com.bennyhuo.kotlin.deepcopy.builtin" else packageName 65 | 66 | 67 | val KTypeElement.escapedPackageName: String 68 | get() = escapeStdlibPackageName(packageName()) 69 | 70 | private val supportedCollectionTypes = setOf( 71 | "java.util.Collection", 72 | "java.util.List", 73 | "java.util.Set" 74 | ) 75 | 76 | private val supportedMapTypes = setOf( 77 | "java.util.Map" 78 | ) 79 | 80 | val TypeElement.isSupportedCollectionType: Boolean 81 | get() = this.qualifiedName.toString() in supportedCollectionTypes 82 | 83 | val TypeElement.isSupportedMapType: Boolean 84 | get() = this.qualifiedName.toString() in supportedMapTypes 85 | 86 | const val RUNTIME_PACKAGE = "com.bennyhuo.kotlin.deepcopy.runtime" 87 | 88 | val DEEPCOPYABLE_TYPENAME = ClassName.bestGuess("com.bennyhuo.kotlin.deepcopy.DeepCopyable") 89 | 90 | fun TypeName.isDeepCopyTypeVariable(): Boolean { 91 | contract { 92 | returns(true) implies (this@isDeepCopyTypeVariable is TypeVariableName) 93 | } 94 | return (this as? TypeVariableName)?.bounds?.any { 95 | (it is ClassName && it == DEEPCOPYABLE_TYPENAME) || 96 | (it is ParameterizedTypeName && it.rawType == DEEPCOPYABLE_TYPENAME) 97 | } == true 98 | } -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/resources/META-INF/gradle/incremental.annotation.processors: -------------------------------------------------------------------------------- 1 | com.bennyhuo.kotlin.deepcopy.compiler.apt.DeepCopyProcessor,aggregating -------------------------------------------------------------------------------- /compiler/compiler-apt/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | com.bennyhuo.kotlin.deepcopy.compiler.apt.DeepCopyProcessor -------------------------------------------------------------------------------- /compiler/compiler-ksp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | kotlin("jvm") 5 | } 6 | 7 | dependencies { 8 | implementation("com.google.devtools.ksp:symbol-processing-api:$kspVersion") 9 | implementation("com.squareup:kotlinpoet:$kotlinPoetVersion") 10 | implementation("com.squareup:kotlinpoet-ksp:$kotlinPoetVersion") 11 | 12 | implementation("com.bennyhuo.kotlin:ksp-module-support:$moduleSupportVersion") 13 | 14 | implementation(project(":annotations")) 15 | } 16 | 17 | tasks.withType().configureEach { 18 | kotlinOptions { 19 | freeCompilerArgs = listOf( 20 | "-Xopt-in=com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview", 21 | "-Xopt-in=com.google.devtools.ksp.KspExperimental", 22 | "-Xopt-in=kotlin.contracts.ExperimentalContracts" 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /compiler/compiler-ksp/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=deepcopy-compiler-ksp 2 | POM_NAME=KotlinDeepCopy-Compiler-Ksp -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/DeepCopyConfigIndex.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp 2 | 3 | import com.google.devtools.ksp.symbol.KSAnnotated 4 | import com.google.devtools.ksp.symbol.KSClassDeclaration 5 | import com.google.devtools.ksp.symbol.KSDeclaration 6 | import com.google.devtools.ksp.symbol.KSType 7 | 8 | class DeepCopyConfigIndex(configs: Collection?, configsFromLibrary: Collection?) { 9 | 10 | companion object { 11 | val instance: DeepCopyConfigIndex 12 | get() = currentInstance!! 13 | 14 | private var currentInstance: DeepCopyConfigIndex? = null 15 | 16 | operator fun contains(declaration: KSDeclaration): Boolean { 17 | return declaration in instance.deepCopyClassDeclarations 18 | || declaration in instance.deepCopyClassDeclarationsFromLibraryConfigs 19 | } 20 | 21 | fun release() { 22 | currentInstance = null 23 | } 24 | } 25 | 26 | init { 27 | currentInstance = this 28 | } 29 | 30 | val deepCopyClassDeclarations = parseConfigs(configs) 31 | 32 | val deepCopyClassDeclarationsFromLibraryConfigs = parseConfigs(configsFromLibrary) 33 | private fun parseConfigs(configs: Collection?): Set { 34 | return configs?.filterIsInstance() 35 | ?.flatMap { 36 | it.annotations 37 | }?.flatMap { 38 | it.arguments 39 | }?.flatMap { 40 | when (val value = it.value) { 41 | is List<*> -> value.asSequence() 42 | else -> sequenceOf(value) 43 | } 44 | }?.filterIsInstance() 45 | ?.map { it.declaration } 46 | ?.filterIsInstance() 47 | ?.toSet() 48 | ?: emptySet() 49 | } 50 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/DeepCopyGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.adapter.Adapter 4 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.loop.DeepCopyLoopDetector 5 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.meta.KComponent 6 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.utils.Platform 7 | import com.google.devtools.ksp.processing.Resolver 8 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 9 | import com.google.devtools.ksp.symbol.KSClassDeclaration 10 | import com.squareup.kotlinpoet.FileSpec 11 | import com.squareup.kotlinpoet.FunSpec 12 | import com.squareup.kotlinpoet.ParameterSpec 13 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 14 | import com.squareup.kotlinpoet.TypeVariableName 15 | import com.squareup.kotlinpoet.ksp.addOriginatingKSFile 16 | import com.squareup.kotlinpoet.ksp.toClassName 17 | import com.squareup.kotlinpoet.ksp.toTypeName 18 | import com.squareup.kotlinpoet.ksp.toTypeParameterResolver 19 | import com.squareup.kotlinpoet.ksp.toTypeVariableName 20 | import com.squareup.kotlinpoet.ksp.writeTo 21 | 22 | /** 23 | * Created by benny. 24 | */ 25 | class DeepCopyGenerator( 26 | val env: SymbolProcessorEnvironment, 27 | ) { 28 | 29 | fun generate(resolver: Resolver, deepCopyTypes: Set) { 30 | val platform = Platform(env) 31 | 32 | deepCopyTypes.forEach { dataClass: KSClassDeclaration -> 33 | 34 | val typeParameterResolver = dataClass.typeParameters.toTypeParameterResolver() 35 | val dataClassName = dataClass.toClassName().let { className -> 36 | if (dataClass.typeParameters.isNotEmpty()) { 37 | className.parameterizedBy( 38 | dataClass.typeParameters.map { 39 | it.toTypeVariableName(typeParameterResolver) 40 | }) 41 | } else className 42 | } 43 | val fileSpecBuilder = FileSpec.builder( 44 | escapeStdlibPackageName(dataClass.packageName.asString()), 45 | "${dataClass.simpleName.asString()}$\$DeepCopy" 46 | ) 47 | val functionBuilder = FunSpec.builder("deepCopy") 48 | .receiver(dataClassName) 49 | .returns(dataClassName) 50 | .also { builder -> 51 | if (platform.isKotlinJvm) builder.addAnnotation(JvmOverloads::class) 52 | } 53 | .addTypeVariables(dataClass.typeParameters.map { 54 | it.toTypeVariableName(typeParameterResolver).let { TypeVariableName(it.name, it.bounds) } 55 | }).also { builder -> 56 | dataClass.containingFile?.let { builder.addOriginatingKSFile(it) } 57 | } 58 | 59 | val statementStringBuilder = StringBuilder("%T(") 60 | 61 | dataClass.primaryConstructor!!.parameters.forEach { parameter -> 62 | val adapter = Adapter(KComponent( 63 | parameter, parameter.type.toTypeName(typeParameterResolver) 64 | )) 65 | adapter.addImport(fileSpecBuilder) 66 | adapter.addStatement(statementStringBuilder) 67 | 68 | functionBuilder.addParameter( 69 | ParameterSpec.builder( 70 | parameter.name!!.asString(), 71 | parameter.type.toTypeName(typeParameterResolver) 72 | ).defaultValue("this.${parameter.name!!.asString()}").build() 73 | ) 74 | } 75 | 76 | statementStringBuilder.setCharAt(statementStringBuilder.lastIndex - 1, ')') 77 | functionBuilder.addStatement( 78 | "return $statementStringBuilder", 79 | dataClassName, 80 | ) 81 | 82 | fileSpecBuilder.addFunction(functionBuilder.build()).build() 83 | .writeTo(env.codeGenerator, false) 84 | 85 | DeepCopyLoopDetector(env, dataClass).detect() 86 | } 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/DeepCopySymbolProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 5 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.loop.DeepCopyLoopException 6 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.utils.LoggerMixin 7 | import com.bennyhuo.kotlin.processor.module.common.MODULE_MIXED 8 | import com.bennyhuo.kotlin.processor.module.ksp.KspModuleProcessor 9 | import com.google.devtools.ksp.processing.Resolver 10 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 11 | import com.google.devtools.ksp.symbol.KSAnnotated 12 | import com.google.devtools.ksp.symbol.KSClassDeclaration 13 | import com.google.devtools.ksp.symbol.Modifier 14 | 15 | /** 16 | * Created by benny at 2021/6/20 19:02. 17 | */ 18 | class DeepCopySymbolProcessor( 19 | env: SymbolProcessorEnvironment 20 | ) : KspModuleProcessor(env), LoggerMixin { 21 | override val annotationsForIndex = setOf(DeepCopyConfig::class.java.name) 22 | 23 | override val processorName: String = "deepCopy" 24 | 25 | override val supportedModuleTypes: Set = setOf(MODULE_MIXED) 26 | 27 | override fun processMain( 28 | resolver: Resolver, 29 | annotatedSymbols: Map>, 30 | annotatedSymbolsFromLibrary: Map> 31 | ): List { 32 | try { 33 | val configIndex = DeepCopyConfigIndex( 34 | annotatedSymbols[DeepCopyConfig::class.java.name], 35 | annotatedSymbolsFromLibrary[DeepCopyConfig::class.java.name], 36 | ) 37 | 38 | val deepCopyTypes = 39 | resolver.getSymbolsWithAnnotation(DeepCopy::class.java.name) 40 | .filterIsInstance() 41 | .filter { Modifier.DATA in it.modifiers } 42 | .toSet() + configIndex.deepCopyClassDeclarations 43 | 44 | logger.warn("DeepCopyTypes: ${deepCopyTypes.joinToString { it.simpleName.asString() }}") 45 | DeepCopyGenerator(env).generate(resolver, deepCopyTypes) 46 | 47 | DeepCopyConfigIndex.release() 48 | } catch (e: Exception) { 49 | if (e is DeepCopyLoopException) { 50 | logger.error(e.message!!, e.declaration) 51 | } else { 52 | logger.exception(e) 53 | } 54 | } 55 | 56 | return emptyList() 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/DeepCopySymbolProcessorProvider.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp 2 | 3 | import com.google.devtools.ksp.processing.SymbolProcessor 4 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 5 | import com.google.devtools.ksp.processing.SymbolProcessorProvider 6 | 7 | /** 8 | * Created by benny at 2021/6/20 19:03. 9 | */ 10 | class DeepCopySymbolProcessorProvider: SymbolProcessorProvider { 11 | override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { 12 | return DeepCopySymbolProcessor(environment) 13 | } 14 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/adapter/BaseAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp.adapter 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.isSupportedCollectionType 4 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.isSupportedMapType 5 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.meta.KComponent 6 | import com.squareup.kotlinpoet.FileSpec 7 | 8 | /** 9 | * Created by benny. 10 | */ 11 | abstract class BaseAdapter( 12 | val component: KComponent 13 | ) { 14 | 15 | protected val nullableMark = if (component.type.isNullable) "?" else "" 16 | 17 | abstract fun addImport(builder: FileSpec.Builder) 18 | 19 | abstract fun addStatement(builder: StringBuilder) 20 | 21 | } 22 | 23 | fun Adapter(component: KComponent): BaseAdapter { 24 | return when { 25 | component.isDeepCopyable -> { 26 | DeepCopyAdapter(component) 27 | } 28 | component.declaration.isSupportedCollectionType -> { 29 | DeepCopyableCollectionAdapter(component) 30 | } 31 | component.declaration.isSupportedMapType -> { 32 | DeepCopyableMapAdapter(component) 33 | } 34 | else -> { 35 | NoDeepCopyAdapter(component) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/adapter/CollectionAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp.adapter 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.RUNTIME_PACKAGE 4 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.deepCopyable 5 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.escapedPackageName 6 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.meta.KComponent 7 | import com.squareup.kotlinpoet.FileSpec 8 | 9 | /** 10 | * Created by benny. 11 | */ 12 | abstract class BaseCollectionAdapter(component: KComponent) : BaseAdapter(component) { 13 | 14 | 15 | override fun addImport(builder: FileSpec.Builder) { 16 | builder.addImport(RUNTIME_PACKAGE, "deepCopy") 17 | } 18 | 19 | override fun addStatement(builder: StringBuilder) { 20 | builder.append("${component.name}${nullableMark}.${deepCopyCall()}, ") 21 | } 22 | 23 | abstract fun deepCopyCall(): String 24 | 25 | } 26 | 27 | class DeepCopyableCollectionAdapter(component: KComponent) : BaseCollectionAdapter(component) { 28 | 29 | override fun addImport(builder: FileSpec.Builder) { 30 | super.addImport(builder) 31 | 32 | val elementType = component.ksType.arguments.single().type!!.resolve().declaration 33 | if (elementType.deepCopyable) { 34 | builder.addImport(elementType.escapedPackageName, "deepCopy") 35 | } 36 | } 37 | 38 | override fun deepCopyCall(): String { 39 | return "deepCopy${ 40 | if (component.isTypeArgumentDeepCopyable(0)) " { it.deepCopy() }" 41 | else "()" 42 | }" 43 | } 44 | } 45 | 46 | class DeepCopyableMapAdapter(component: KComponent) : BaseCollectionAdapter(component) { 47 | private val bodyForDeepCopyable = "{ it.deepCopy() }" 48 | private val bodyForNonDeepCopyable = "{ it }" 49 | 50 | override fun addImport(builder: FileSpec.Builder) { 51 | super.addImport(builder) 52 | 53 | val keyType = component.ksType.arguments[0].type!!.resolve().declaration 54 | if (keyType.deepCopyable) { 55 | builder.addImport(keyType.escapedPackageName, "deepCopy") 56 | } 57 | 58 | val valueType = component.ksType.arguments[1].type!!.resolve().declaration 59 | if (valueType.deepCopyable) { 60 | builder.addImport(valueType.escapedPackageName, "deepCopy") 61 | } 62 | } 63 | 64 | override fun deepCopyCall(): String { 65 | return "deepCopy(${ 66 | (0..1).joinToString { 67 | if (component.isTypeArgumentDeepCopyable(it)) bodyForDeepCopyable 68 | else bodyForNonDeepCopyable 69 | } 70 | })" 71 | } 72 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/adapter/DeepCopyAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp.adapter 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.escapedPackageName 4 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.meta.KComponent 5 | import com.squareup.kotlinpoet.FileSpec 6 | 7 | /** 8 | * Created by benny. 9 | */ 10 | class DeepCopyAdapter(component: KComponent) : BaseAdapter(component) { 11 | override fun addImport(builder: FileSpec.Builder) { 12 | if (component.isDeepCopyableClass) { 13 | builder.addImport(component.declaration.escapedPackageName, "deepCopy") 14 | } 15 | } 16 | 17 | override fun addStatement(builder: StringBuilder) { 18 | builder.append("${component.name}${nullableMark}.deepCopy(), ") 19 | } 20 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/adapter/NoDeepCopyAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp.adapter 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.meta.KComponent 4 | import com.squareup.kotlinpoet.FileSpec 5 | 6 | /** 7 | * Created by benny. 8 | */ 9 | class NoDeepCopyAdapter( 10 | component: KComponent 11 | ): BaseAdapter(component){ 12 | override fun addImport(builder: FileSpec.Builder) = Unit 13 | 14 | override fun addStatement(builder: StringBuilder) { 15 | builder.append("${component.name}, ") 16 | } 17 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/loop/DeepCopyLoopDetector.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp.loop 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.deepCopyable 4 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.utils.LoggerMixin 5 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 6 | import com.google.devtools.ksp.symbol.KSClassDeclaration 7 | import java.util.* 8 | 9 | class DeepCopyLoopDetector( 10 | override val env: SymbolProcessorEnvironment, 11 | private val declaration: KSClassDeclaration 12 | ) : LoggerMixin { 13 | 14 | private val typeStack = Stack() 15 | private val typeSet = HashSet() 16 | 17 | fun detect() { 18 | push(declaration) 19 | detect(declaration, true) 20 | pop() 21 | } 22 | 23 | private fun detect(declaration: KSClassDeclaration, checkNullable: Boolean) { 24 | declaration.primaryConstructor!!.parameters 25 | .map { 26 | it.type.resolve() 27 | }.let { 28 | if (checkNullable) { 29 | it.filter { 30 | // Only nullable types should be checked for top level. 31 | it.isMarkedNullable 32 | } 33 | } else it 34 | }.mapNotNull { 35 | it.declaration as? KSClassDeclaration 36 | }.filter { 37 | it.deepCopyable 38 | }.forEach { 39 | push(it) 40 | detect(it, false) 41 | pop() 42 | } 43 | } 44 | 45 | private fun push(declaration: KSClassDeclaration) { 46 | if (!typeSet.add(declaration)) { 47 | throw DeepCopyLoopException(declaration) 48 | } 49 | typeStack.push(declaration) 50 | } 51 | 52 | private fun pop() { 53 | typeSet.remove(typeStack.pop()) 54 | } 55 | 56 | private fun dumpStack() { 57 | logger.warn("${declaration.qualifiedName!!.asString()}: [${typeStack.joinToString { it.simpleName.asString() }}]") 58 | } 59 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/loop/DeepCopyLoopException.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp.loop 2 | 3 | import com.google.devtools.ksp.symbol.KSClassDeclaration 4 | 5 | class DeepCopyLoopException(val declaration: KSClassDeclaration) 6 | : Exception("Detect infinite copy loop. It will cause stack overflow " + 7 | "to call ${declaration.qualifiedName!!.asString()}.deepCopy() in the runtime.") 8 | -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/meta/KComponent.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp.meta 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.deepCopyable 4 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.isDeepCopyTypeVariable 5 | import com.google.devtools.ksp.symbol.KSValueParameter 6 | import com.squareup.kotlinpoet.TypeName 7 | 8 | /** 9 | * Created by benny. 10 | */ 11 | class KComponent( 12 | val valueParameter: KSValueParameter, 13 | val type: TypeName 14 | ) { 15 | 16 | val name: String 17 | get() = valueParameter.name!!.asString() 18 | 19 | val ksType by lazy { 20 | valueParameter.type.resolve() 21 | } 22 | 23 | val declaration by lazy { 24 | ksType.declaration 25 | } 26 | 27 | val isDeepCopyableClass: Boolean by lazy { 28 | declaration.deepCopyable 29 | } 30 | 31 | val isDeepCopyableTypeVariable: Boolean by lazy { 32 | type.isDeepCopyTypeVariable() 33 | } 34 | 35 | val isDeepCopyable: Boolean by lazy { 36 | isDeepCopyableClass || isDeepCopyableTypeVariable 37 | } 38 | 39 | fun isTypeArgumentDeepCopyableClass(index: Int): Boolean { 40 | if (ksType.arguments.isEmpty()) return false 41 | 42 | return ksType.arguments[index].let {arg -> 43 | arg.type?.resolve()?.declaration?.deepCopyable == true 44 | } 45 | } 46 | 47 | fun isTypeArgumentDeepCopyable(index: Int): Boolean { 48 | if (ksType.arguments.isEmpty()) return false 49 | 50 | return ksType.arguments[index].let {arg -> 51 | arg.type?.resolve()?.declaration?.let { 52 | it.deepCopyable || it.isDeepCopyTypeVariable() 53 | } == true 54 | } 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/utils/LoggerMixin.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp.utils 2 | 3 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 4 | 5 | /** 6 | * Created by benny. 7 | */ 8 | interface LoggerMixin { 9 | 10 | val env: SymbolProcessorEnvironment 11 | 12 | val logger 13 | get() = env.logger 14 | 15 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/utils/Platform.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp.utils 2 | 3 | import com.google.devtools.ksp.processing.JvmPlatformInfo 4 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 5 | 6 | /** 7 | * Created by benny. 8 | */ 9 | class Platform(private val env: SymbolProcessorEnvironment) { 10 | 11 | val isKotlinJvm by lazy { 12 | env.platforms.singleOrNull { it is JvmPlatformInfo } != null 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/ksp/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.ksp 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | import com.google.devtools.ksp.isAnnotationPresent 5 | import com.google.devtools.ksp.symbol.KSClassDeclaration 6 | import com.google.devtools.ksp.symbol.KSDeclaration 7 | import com.google.devtools.ksp.symbol.KSTypeParameter 8 | import com.squareup.kotlinpoet.ClassName 9 | import com.squareup.kotlinpoet.ParameterizedTypeName 10 | import com.squareup.kotlinpoet.TypeName 11 | import com.squareup.kotlinpoet.TypeVariableName 12 | import kotlin.contracts.contract 13 | 14 | inline fun escapeStdlibPackageName(packageName: String) = 15 | if (packageName == "kotlin") "com.bennyhuo.kotlin.deepcopy.builtin" else packageName 16 | 17 | 18 | val KSDeclaration.escapedPackageName: String 19 | get() = escapeStdlibPackageName(packageName.asString()) 20 | 21 | val KSDeclaration.deepCopyable: Boolean 22 | get() = this is KSClassDeclaration && 23 | (isAnnotationPresent(DeepCopy::class) || this in DeepCopyConfigIndex) 24 | 25 | 26 | fun KSDeclaration.isDeepCopyTypeVariable(): Boolean { 27 | contract { 28 | returns(true) implies (this@isDeepCopyTypeVariable is KSTypeParameter) 29 | } 30 | return this is KSTypeParameter && this.bounds.any { 31 | it.resolve().declaration.qualifiedName?.asString() == DEEPCOPYABLE_FULL_NAME 32 | } 33 | } 34 | 35 | private val supportedCollectionTypes = setOf( 36 | "kotlin.collections.Collection", 37 | "kotlin.collections.Set", 38 | "kotlin.collections.List", 39 | "kotlin.collections.MutableCollection", 40 | "kotlin.collections.MutableSet", 41 | "kotlin.collections.MutableList" 42 | ) 43 | 44 | private val supportedMapTypes = setOf( 45 | "kotlin.collections.Map", 46 | "kotlin.collections.MutableMap" 47 | ) 48 | 49 | val KSDeclaration.isSupportedCollectionType: Boolean 50 | get() = this.qualifiedName?.asString() in supportedCollectionTypes 51 | 52 | val KSDeclaration.isSupportedMapType: Boolean 53 | get() = this.qualifiedName?.asString() in supportedMapTypes 54 | 55 | const val RUNTIME_PACKAGE = "com.bennyhuo.kotlin.deepcopy.runtime" 56 | 57 | val DEEPCOPYABLE_FULL_NAME = "com.bennyhuo.kotlin.deepcopy.DeepCopyable" 58 | val DEEPCOPYABLE_TYPENAME = ClassName.bestGuess("com.bennyhuo.kotlin.deepcopy.DeepCopyable") 59 | 60 | fun TypeName.isDeepCopyTypeVariable(): Boolean { 61 | contract { 62 | returns(true) implies (this@isDeepCopyTypeVariable is TypeVariableName) 63 | } 64 | return (this as? TypeVariableName)?.bounds?.any { 65 | (it is ClassName && it == DEEPCOPYABLE_TYPENAME) || 66 | (it is ParameterizedTypeName && it.rawType == DEEPCOPYABLE_TYPENAME) 67 | } == true 68 | } -------------------------------------------------------------------------------- /compiler/compiler-ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider: -------------------------------------------------------------------------------- 1 | com.bennyhuo.kotlin.deepcopy.compiler.ksp.DeepCopySymbolProcessorProvider -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | 3 | VERSION_NAME=1.9.20-1.0.1 4 | 5 | GROUP=com.bennyhuo.kotlin 6 | 7 | POM_NAME=KotlinDeepCopy 8 | 9 | POM_URL=https://github.com/bennyhuo/KotlinDeepCopy 10 | POM_DESCRIPTION=Provide deepCopy extension for data classes. 11 | 12 | POM_SCM_URL=https://github.com/bennyhuo/KotlinDeepCopy 13 | POM_SCM_CONNECTION=scm:git:git://github.com/bennyhuo/KotlinDeepCopy.git 14 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com:bennyhuo/KotlinDeepCopy.git 15 | 16 | POM_LICENCE_NAME=MIT License 17 | POM_LICENCE_URL=https://github.com/bennyhuo/KotlinDeepCopy/blob/master/LICENSE 18 | POM_LICENCE_DIST=repo 19 | 20 | POM_DEVELOPER_ID=bennyhuo 21 | POM_DEVELOPER_NAME=Benny Huo 22 | POM_DEVELOPER_URL=https://github.com/bennyhuo/ 23 | 24 | SONATYPE_HOST=S01 25 | RELEASE_SIGNING_ENABLED=true 26 | SONATYPE_AUTOMATIC_RELEASE=true 27 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bennyhuo/KotlinDeepCopy/f8f8d54920108f1faca6522bac6e1a1ef9243c02/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-7.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | clear 3 | ./gradlew :reflect-impl:clean :reflect-impl:assemble :reflect-impl:generatePomFileForMavenPublication :reflect-impl:publishMavenPublicationToMavenLocal 4 | ./gradlew :annotations:clean :annotations:assemble :annotations:generatePomFileForMavenPublication :annotations:publishMavenPublicationToMavenLocal 5 | ./gradlew :apt-impl:compiler:clean :apt-impl:compiler:assemble :apt-impl:compiler:generatePomFileForMavenPublication :apt-impl:compiler:publishMavenPublicationToMavenLocal -------------------------------------------------------------------------------- /kcp-impl/build.gradle.kts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bennyhuo/KotlinDeepCopy/f8f8d54920108f1faca6522bac6e1a1ef9243c02/kcp-impl/build.gradle.kts -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp-embeddable/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | id("com.bennyhuo.kotlin.plugin.embeddable") 4 | } 5 | 6 | dependencies { 7 | embedded(project(":kcp-impl:compiler-kcp")) 8 | } -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp-embeddable/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=deepcopy-compiler-kcp-embeddable 2 | POM_NAME=KotlinDeepCopy-Compiler-Kcp-Embeddable -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | kotlin("jvm") 5 | java 6 | kotlin("kapt") 7 | id("com.github.gmazzo.buildconfig") 8 | id("com.bennyhuo.kotlin.plugin.embeddable.test") 9 | } 10 | 11 | dependencies { 12 | compileOnly("org.jetbrains.kotlin:kotlin-stdlib") 13 | compileOnly("org.jetbrains.kotlin:kotlin-compiler") 14 | 15 | kapt("com.google.auto.service:auto-service:1.0.1") 16 | compileOnly("com.google.auto.service:auto-service-annotations:1.0.1") 17 | 18 | testImplementation(project(":annotations")) 19 | testImplementation(project(":runtime")) 20 | 21 | testImplementation(kotlin("test-junit")) 22 | testImplementation("org.jetbrains.kotlin:kotlin-compiler-embeddable") 23 | 24 | testImplementation("com.bennyhuo.kotlin:kotlin-compile-testing-extensions:$compileTestingExtensionsVersion") 25 | } 26 | 27 | val compileKotlin: KotlinCompile by tasks 28 | compileKotlin.kotlinOptions.freeCompilerArgs += listOf("-opt-in=kotlin.RequiresOptIn") 29 | 30 | buildConfig { 31 | packageName("$group.kcp") 32 | buildConfigField("String", "KOTLIN_PLUGIN_ID", "\"${project.properties["KOTLIN_PLUGIN_ID"]}\"") 33 | } 34 | -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=deepcopy-compiler-kcp 2 | POM_NAME=KotlinDeepCopy-Compiler-Kcp -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/DeepCopyCommandLineProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp 2 | 3 | import com.bennyhuo.kotlin.kcp.BuildConfig 4 | import com.google.auto.service.AutoService 5 | import org.jetbrains.kotlin.compiler.plugin.CliOption 6 | import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor 7 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 8 | 9 | @OptIn(ExperimentalCompilerApi::class) 10 | @AutoService(CommandLineProcessor::class) 11 | class DeepCopyCommandLineProcessor : CommandLineProcessor { 12 | 13 | override val pluginId: String = BuildConfig.KOTLIN_PLUGIN_ID 14 | 15 | override val pluginOptions: Collection = emptyList() 16 | 17 | } 18 | -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/DeepCopyComponentRegistrar.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.checker.DeepCopyComponentContainerContributor 4 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.ir.DeepCopyIrGenerationExtension 5 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.symbol.DeepCopyResolveExtension 6 | import com.google.auto.service.AutoService 7 | import com.intellij.mock.MockProject 8 | import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension 9 | import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar 10 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 11 | import org.jetbrains.kotlin.config.CompilerConfiguration 12 | import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor 13 | import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension 14 | 15 | @OptIn(ExperimentalCompilerApi::class) 16 | @AutoService(ComponentRegistrar::class) 17 | class DeepCopyComponentRegistrar : ComponentRegistrar { 18 | 19 | override fun registerProjectComponents( 20 | project: MockProject, 21 | configuration: CompilerConfiguration 22 | ) { 23 | SyntheticResolveExtension.registerExtension( 24 | project, 25 | DeepCopyResolveExtension() 26 | ) 27 | IrGenerationExtension.registerExtension( 28 | project, 29 | DeepCopyIrGenerationExtension() 30 | ) 31 | StorageComponentContainerContributor.registerExtension( 32 | project, 33 | DeepCopyComponentContainerContributor() 34 | ) 35 | } 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/PluginAvailability.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp 2 | 3 | import com.intellij.psi.PsiElement 4 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 5 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 6 | import org.jetbrains.kotlin.ir.declarations.IrClass 7 | import org.jetbrains.kotlin.resolve.source.PsiSourceElement 8 | 9 | /** 10 | * Created by benny at 2022/1/13 2:15 PM. 11 | */ 12 | interface PluginAvailability { 13 | fun PsiElement.isDeepCopyPluginEnabled(): Boolean = true 14 | 15 | fun ModuleDescriptor.isDeepCopyPluginEnabled() = true 16 | 17 | fun ClassDescriptor.isDeepCopyPluginEnabled(): Boolean { 18 | val sourceElement = (source as? PsiSourceElement)?.psi ?: return false 19 | return sourceElement.isDeepCopyPluginEnabled() 20 | } 21 | 22 | fun IrClass.isDeepCopyPluginEnabled(): Boolean { 23 | val sourceElement = (source as? PsiSourceElement)?.psi ?: return false 24 | return sourceElement.isDeepCopyPluginEnabled() 25 | } 26 | } -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/checker/DeepCopyComponentContainerContributor.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp.checker 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.PluginAvailability 4 | import org.jetbrains.kotlin.container.StorageComponentContainer 5 | import org.jetbrains.kotlin.container.useInstance 6 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 7 | import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor 8 | import org.jetbrains.kotlin.platform.TargetPlatform 9 | 10 | open class DeepCopyComponentContainerContributor : StorageComponentContainerContributor, 11 | PluginAvailability { 12 | override fun registerModuleComponents( 13 | container: StorageComponentContainer, platform: TargetPlatform, moduleDescriptor: ModuleDescriptor 14 | ) { 15 | if (moduleDescriptor.isDeepCopyPluginEnabled()) { 16 | container.useInstance(DeepCopyDeclarationChecker()) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/checker/DeepCopyDeclarationChecker.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp.checker 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.annotatedAsDeepCopyableDataClass 4 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.collectionTypes 5 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.implementsDeepCopyableInterface 6 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.userType 7 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.utils.isDeepCopyable 8 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 9 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 10 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 11 | import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName 12 | import org.jetbrains.kotlin.psi.KtClass 13 | import org.jetbrains.kotlin.psi.KtDeclaration 14 | import org.jetbrains.kotlin.psi.KtUserType 15 | import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker 16 | import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext 17 | import org.jetbrains.kotlin.types.KotlinType 18 | import org.jetbrains.kotlin.types.SimpleType 19 | import org.jetbrains.kotlin.types.typeUtil.isTypeParameter 20 | 21 | /** 22 | * Created by benny at 2022/1/14 3:49 PM. 23 | */ 24 | class DeepCopyDeclarationChecker : DeclarationChecker { 25 | override fun check( 26 | declaration: KtDeclaration, 27 | descriptor: DeclarationDescriptor, 28 | context: DeclarationCheckerContext 29 | ) { 30 | if ( 31 | descriptor is ClassDescriptor 32 | && declaration is KtClass 33 | && descriptor.isData 34 | && (descriptor.implementsDeepCopyableInterface() || descriptor.annotatedAsDeepCopyableDataClass()) 35 | ) { 36 | val parameterDeclarations = declaration.primaryConstructorParameters 37 | descriptor.unsubstitutedPrimaryConstructor 38 | ?.valueParameters 39 | ?.forEachIndexed { index, value -> 40 | val userType = parameterDeclarations[index].typeReference?.userType() 41 | checkType(value.type, context, userType) 42 | } 43 | } 44 | } 45 | 46 | private fun checkType( 47 | type: KotlinType, 48 | context: DeclarationCheckerContext, 49 | userType: KtUserType? 50 | ) { 51 | if (userType == null) return 52 | if (KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(type)) return 53 | if (KotlinBuiltIns.isString(type)) return 54 | if (type !is SimpleType) return 55 | 56 | val fqName = if (type.isTypeParameter()) { 57 | type.toString() 58 | } else { 59 | val fqName = type.getJetTypeFqName(false) 60 | if (fqName in collectionTypes) { 61 | val typeArgument = userType.typeArguments.single().typeReference?.userType() ?: return 62 | checkType(type.arguments.single().type, context, typeArgument) 63 | return 64 | } 65 | fqName 66 | } 67 | 68 | if (!type.isDeepCopyable()) { 69 | context.trace.report( 70 | ErrorsDeepCopy.TYPE_NOT_IMPLEMENT_DEEPCOPYABLE.on(userType, fqName) 71 | ) 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/checker/DefaultErrorMessagesDeepCopy.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp.checker 2 | 3 | import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages 4 | import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap 5 | import org.jetbrains.kotlin.diagnostics.rendering.Renderers 6 | 7 | object DefaultErrorMessagesDeepCopy : DefaultErrorMessages.Extension { 8 | private val rendererMap = DiagnosticFactoryToRendererMap("DeepCopy") 9 | override fun getMap() = rendererMap 10 | 11 | init { 12 | rendererMap.put( 13 | ErrorsDeepCopy.TYPE_NOT_IMPLEMENT_DEEPCOPYABLE, 14 | "''{0}'' should implement ''com.bennyhuo.kotlin.deepcopy.DeepCopyable'' to support deep copy.", 15 | Renderers.TO_STRING 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/checker/ErrorsDeepCopy.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp.checker 2 | 3 | import com.intellij.psi.PsiElement 4 | import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1 5 | import org.jetbrains.kotlin.diagnostics.Errors 6 | import org.jetbrains.kotlin.diagnostics.Severity 7 | 8 | interface ErrorsDeepCopy { 9 | companion object { 10 | @JvmField 11 | val TYPE_NOT_IMPLEMENT_DEEPCOPYABLE = DiagnosticFactory1.create(Severity.WARNING) 12 | 13 | init { 14 | Errors.Initializer.initializeFactoryNamesAndDefaultErrorMessages( 15 | ErrorsDeepCopy::class.java, 16 | DefaultErrorMessagesDeepCopy 17 | ) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/ir/DeepCopyClassTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp.ir 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.PluginAvailability 4 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.annotatedAsDeepCopyableDataClass 5 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.deepCopyFunctionForDataClass 6 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.deepCopyFunctionForDeepCopyable 7 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.implementsDeepCopyableInterface 8 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.irGetProperty 9 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.irThis 10 | import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext 11 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 12 | import org.jetbrains.kotlin.ir.IrStatement 13 | import org.jetbrains.kotlin.ir.builders.irGet 14 | import org.jetbrains.kotlin.ir.declarations.IrClass 15 | import org.jetbrains.kotlin.ir.declarations.IrFunction 16 | import org.jetbrains.kotlin.ir.declarations.createExpressionBody 17 | import org.jetbrains.kotlin.ir.util.properties 18 | 19 | class DeepCopyClassTransformer( 20 | private val pluginContext: IrPluginContext, 21 | private val pluginAvailability: PluginAvailability 22 | ) : IrElementTransformerVoidWithContext(), PluginAvailability by pluginAvailability { 23 | 24 | override fun visitClassNew(declaration: IrClass): IrStatement { 25 | if (!declaration.isDeepCopyPluginEnabled()) return super.visitClassNew(declaration) 26 | 27 | if (declaration.annotatedAsDeepCopyableDataClass()) { 28 | declaration.deepCopyFunctionForDataClass()?.takeIf { 29 | it.body == null 30 | }?.let { function -> 31 | generateDeepCopyFunctionForDataClass(declaration, function) 32 | } 33 | } 34 | // Only generate implementation for data classes. 35 | if (declaration.isData && declaration.implementsDeepCopyableInterface()) { 36 | declaration.deepCopyFunctionForDeepCopyable()?.takeIf { 37 | it.body == null 38 | }?.let { function -> 39 | generateDeepCopyFunctionForDeepCopyable(declaration, function) 40 | } 41 | } 42 | return super.visitClassNew(declaration) 43 | } 44 | 45 | private fun generateDeepCopyFunctionForDataClass( 46 | irClass: IrClass, 47 | irFunction: IrFunction 48 | ) { 49 | DeepCopyFunctionBuilder( 50 | irClass, 51 | irFunction, 52 | pluginContext 53 | ).generateBody { constructorParameter -> 54 | val irValueParameter = irFunction.valueParameters[constructorParameter.index] 55 | irGet(irValueParameter.type, irValueParameter.symbol) 56 | }.generateDefaultParameter { parameter -> 57 | pluginContext.irFactory.createExpressionBody( 58 | irGetProperty( 59 | irFunction.irThis(), 60 | irClass.properties.single { it.name == parameter.name } 61 | ) 62 | ) 63 | } 64 | } 65 | 66 | private fun generateDeepCopyFunctionForDeepCopyable( 67 | irClass: IrClass, 68 | irFunction: IrFunction 69 | ) { 70 | DeepCopyFunctionBuilder( 71 | irClass, 72 | irFunction, 73 | pluginContext 74 | ).generateBody { constructorParameter -> 75 | irGetProperty( 76 | irFunction.irThis(), 77 | irClass.properties.first { it.name == constructorParameter.name } 78 | ) 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/ir/DeepCopyFunctionBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp.ir 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.copyFunctionForDataClass 4 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.deepCopyFunctionForCollections 5 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.deepCopyFunctionForDataClass 6 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.deepCopyFunctionForDeepCopyable 7 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 8 | import org.jetbrains.kotlin.ir.builders.IrBlockBodyBuilder 9 | import org.jetbrains.kotlin.ir.builders.Scope 10 | import org.jetbrains.kotlin.ir.builders.irCall 11 | import org.jetbrains.kotlin.ir.builders.irReturn 12 | import org.jetbrains.kotlin.ir.declarations.IrClass 13 | import org.jetbrains.kotlin.ir.declarations.IrFunction 14 | import org.jetbrains.kotlin.ir.declarations.IrValueParameter 15 | import org.jetbrains.kotlin.ir.expressions.IrExpression 16 | import org.jetbrains.kotlin.ir.expressions.IrExpressionBody 17 | import org.jetbrains.kotlin.ir.types.IrType 18 | import org.jetbrains.kotlin.ir.types.getClass 19 | import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET 20 | import org.jetbrains.kotlin.ir.util.defaultType 21 | import org.jetbrains.kotlin.ir.util.isTypeParameter 22 | import org.jetbrains.kotlin.ir.util.primaryConstructor 23 | 24 | class DeepCopyFunctionBuilder( 25 | private val irClass: IrClass, 26 | private val irFunction: IrFunction, 27 | private val pluginContext: IrPluginContext, 28 | startOffset: Int = SYNTHETIC_OFFSET, 29 | endOffset: Int = SYNTHETIC_OFFSET, 30 | ) : IrBlockBodyBuilder(pluginContext, Scope(irFunction.symbol), startOffset, endOffset) { 31 | 32 | init { 33 | irFunction.body = doBuild() 34 | } 35 | 36 | fun generateDefaultParameter( 37 | valueMapper: DeepCopyFunctionBuilder.(IrValueParameter) -> IrExpressionBody? 38 | ): DeepCopyFunctionBuilder { 39 | irFunction.valueParameters.forEach { irValueParameter -> 40 | irValueParameter.defaultValue = valueMapper(irValueParameter) 41 | } 42 | return this 43 | } 44 | 45 | fun generateBody( 46 | valueParameterMapper: DeepCopyFunctionBuilder.(IrValueParameter) -> IrExpression 47 | ): DeepCopyFunctionBuilder { 48 | val primaryConstructor = irClass.primaryConstructor!! 49 | +irReturn( 50 | irCall( 51 | primaryConstructor.symbol, 52 | irClass.defaultType, 53 | constructedClass = irClass 54 | ).apply { 55 | symbol.owner.valueParameters.forEachIndexed { index, param -> 56 | putValueArgument(index, param.type.tryDeepCopy(valueParameterMapper(param))) 57 | } 58 | } 59 | ) 60 | return this 61 | } 62 | 63 | private fun IrType.tryDeepCopy( 64 | irExpression: IrExpression 65 | ): IrExpression { 66 | if (this.isTypeParameter()) { 67 | val deepCopyFunction = this.deepCopyFunctionForDeepCopyable(pluginContext) 68 | if (deepCopyFunction != null) { 69 | return irCall(deepCopyFunction).apply { 70 | dispatchReceiver = irExpression 71 | } 72 | } 73 | } 74 | 75 | val irClass = this.getClass() ?: return irExpression 76 | with(irClass) { 77 | val possibleCopyFunction = deepCopyFunctionForDataClass() 78 | ?: deepCopyFunctionForDeepCopyable() 79 | ?: copyFunctionForDataClass() 80 | 81 | return if (possibleCopyFunction != null) { 82 | irCall(possibleCopyFunction).apply { 83 | dispatchReceiver = irExpression 84 | } 85 | } else { 86 | val deepCopyFunction = deepCopyFunctionForCollections(pluginContext) 87 | if (deepCopyFunction == null) { 88 | irExpression 89 | } else { 90 | irCall(deepCopyFunction).apply { 91 | extensionReceiver = irExpression 92 | } 93 | } 94 | } 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/ir/DeepCopyIrGenerationExtension.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp.ir 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.PluginAvailability 4 | import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension 5 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 6 | import org.jetbrains.kotlin.ir.declarations.IrModuleFragment 7 | import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid 8 | 9 | open class DeepCopyIrGenerationExtension : IrGenerationExtension, PluginAvailability { 10 | override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { 11 | moduleFragment.transformChildrenVoid(DeepCopyClassTransformer(pluginContext, this)) 12 | } 13 | } -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/symbol/DeepCopyFunctionDescriptorImpl.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp.symbol 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.DEEP_COPY_FUNCTION_NAME 4 | import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor 5 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 6 | import org.jetbrains.kotlin.descriptors.DescriptorVisibilities 7 | import org.jetbrains.kotlin.descriptors.Modality 8 | import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor 9 | import org.jetbrains.kotlin.descriptors.annotations.Annotations 10 | import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl 11 | import org.jetbrains.kotlin.name.Name 12 | 13 | /** 14 | * Created by benny at 2022/1/10 6:42 PM. 15 | */ 16 | class DeepCopyFunctionDescriptorImpl( 17 | private val classDescriptor: ClassDescriptor 18 | ) : SimpleFunctionDescriptorImpl( 19 | classDescriptor, 20 | null, 21 | Annotations.EMPTY, 22 | Name.identifier(DEEP_COPY_FUNCTION_NAME), 23 | CallableMemberDescriptor.Kind.SYNTHESIZED, 24 | classDescriptor.source 25 | ) { 26 | fun initialize( 27 | valueParameters: List = emptyList() 28 | ) { 29 | super.initialize( 30 | null, 31 | classDescriptor.thisAsReceiverParameter, 32 | emptyList(), 33 | valueParameters, 34 | classDescriptor.defaultType, 35 | Modality.FINAL, 36 | DescriptorVisibilities.PUBLIC 37 | ) 38 | } 39 | } -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/symbol/DeepCopyResolveExtension.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp.symbol 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.DEEP_COPY_FUNCTION_NAME 4 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.PluginAvailability 5 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.annotatedAsDeepCopyableDataClass 6 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.copy 7 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.deepCopyableType 8 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.implementsDeepCopyableInterface 9 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 10 | import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor 11 | import org.jetbrains.kotlin.js.inline.util.zipWithDefault 12 | import org.jetbrains.kotlin.name.Name 13 | import org.jetbrains.kotlin.resolve.BindingContext 14 | import org.jetbrains.kotlin.resolve.descriptorUtil.module 15 | import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension 16 | import org.jetbrains.kotlin.types.KotlinType 17 | import org.jetbrains.kotlin.types.KotlinTypeFactory 18 | import org.jetbrains.kotlin.types.TypeAttributes 19 | import org.jetbrains.kotlin.types.TypeProjectionImpl 20 | 21 | /** 22 | * Created by benny at 2021/6/25 8:01. 23 | */ 24 | open class DeepCopyResolveExtension : SyntheticResolveExtension, PluginAvailability { 25 | 26 | override fun addSyntheticSupertypes( 27 | thisDescriptor: ClassDescriptor, 28 | supertypes: MutableList 29 | ) { 30 | if (thisDescriptor.isDeepCopyPluginEnabled() && thisDescriptor.annotatedAsDeepCopyableDataClass()) { 31 | supertypes.add( 32 | KotlinTypeFactory.simpleNotNullType( 33 | TypeAttributes.Empty, 34 | thisDescriptor.module.deepCopyableType(), 35 | listOf(TypeProjectionImpl(thisDescriptor.defaultType)) 36 | ) 37 | ) 38 | } else { 39 | super.addSyntheticSupertypes(thisDescriptor, supertypes) 40 | } 41 | } 42 | 43 | override fun getSyntheticFunctionNames(thisDescriptor: ClassDescriptor): List { 44 | if (thisDescriptor.isDeepCopyPluginEnabled() && thisDescriptor.annotatedAsDeepCopyableDataClass()) { 45 | return listOf(Name.identifier(DEEP_COPY_FUNCTION_NAME)) 46 | } 47 | return super.getSyntheticFunctionNames(thisDescriptor) 48 | } 49 | 50 | override fun generateSyntheticMethods( 51 | thisDescriptor: ClassDescriptor, 52 | name: Name, 53 | bindingContext: BindingContext, 54 | fromSupertypes: List, 55 | result: MutableCollection 56 | ) { 57 | if (thisDescriptor.isDeepCopyPluginEnabled()) { 58 | if (name.identifier == DEEP_COPY_FUNCTION_NAME) { 59 | // @DeepCopy 60 | if (thisDescriptor.annotatedAsDeepCopyableDataClass() 61 | && thisDescriptor.unsubstitutedPrimaryConstructor != null 62 | && result.none { 63 | it.typeParameters.isEmpty() && it.valueParameters.zipWithDefault( 64 | thisDescriptor.unsubstitutedPrimaryConstructor!!.valueParameters, null 65 | ).all { it.first?.type == it.second.type } 66 | }) { 67 | result += DeepCopyFunctionDescriptorImpl(thisDescriptor).apply { 68 | initialize(thisDescriptor.unsubstitutedPrimaryConstructor!!.valueParameters.map { 69 | it.copy(this, declaresDefaultValue = true) 70 | }) 71 | } 72 | } 73 | 74 | // data class & DeepCopyable 75 | if (thisDescriptor.isData && thisDescriptor.implementsDeepCopyableInterface() 76 | && result.none { it.typeParameters.isEmpty() && it.valueParameters.isEmpty() } 77 | ) { 78 | result += DeepCopyFunctionDescriptorImpl(thisDescriptor).apply { 79 | initialize() 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/kcp/utils/KotlinType.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler.kcp.utils 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.DEEP_COPY_INTERFACE_NAME 4 | import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName 5 | import org.jetbrains.kotlin.types.KotlinType 6 | import org.jetbrains.kotlin.types.typeUtil.supertypes 7 | 8 | /** 9 | * Created by benny. 10 | */ 11 | fun KotlinType.isDeepCopyable(): Boolean { 12 | return supertypes().any { 13 | it.getJetTypeFqName(false) == DEEP_COPY_INTERFACE_NAME 14 | } 15 | } -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/test/kotlin/com/bennyhuo/kotlin/kcp/deepcopy/compiler/DeepCopyTest.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.kcp.deepcopy.compiler 2 | 3 | import com.bennyhuo.kotlin.compiletesting.extensions.module.KotlinModule 4 | import com.bennyhuo.kotlin.compiletesting.extensions.module.checkResult 5 | import com.bennyhuo.kotlin.compiletesting.extensions.source.FileBasedModuleInfoLoader 6 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.DeepCopyComponentRegistrar 7 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 8 | import org.junit.Test 9 | 10 | @OptIn(ExperimentalCompilerApi::class) 11 | class DeepCopyTest { 12 | 13 | @Test 14 | fun simple() { 15 | testBase("simple.kt") 16 | } 17 | @Test 18 | fun basic() { 19 | testBase("basic.kt") 20 | } 21 | 22 | @Test 23 | fun declaredDeepCopy() { 24 | testBase("declaredDeepCopy.kt") 25 | } 26 | 27 | @Test 28 | fun genericsWithDeepCopyableBounds() { 29 | testBase("GenericsWithDeepCopyableBounds.kt") 30 | } 31 | 32 | @Test 33 | fun deepCopyForInterface() { 34 | testBase("deepCopyForInterface.kt") 35 | } 36 | 37 | @Test 38 | fun collectionElementCheck() { 39 | testBase("collectionElementCheck.kt") 40 | } 41 | 42 | @Test 43 | fun modules() { 44 | testBase("modules.kt") 45 | } 46 | 47 | private fun testBase(fileName: String) { 48 | val loader = FileBasedModuleInfoLoader("testData/$fileName") 49 | loader.loadSourceModuleInfos().map { 50 | KotlinModule(it, componentRegistrars = listOf(DeepCopyComponentRegistrar())) 51 | }.checkResult( 52 | loader.loadExpectModuleInfos(), 53 | checkGeneratedIr = true, 54 | executeEntries = true 55 | ) 56 | } 57 | } -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/test/kotlin/com/bennyhuo/kotlin/kcp/deepcopy/compiler/IrDumpExtension.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.kcp.deepcopy.compiler 2 | 3 | import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension 4 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext 5 | import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar 6 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 7 | import org.jetbrains.kotlin.config.CompilerConfiguration 8 | import org.jetbrains.kotlin.ir.declarations.IrModuleFragment 9 | import org.jetbrains.kotlin.ir.util.dump 10 | import org.jetbrains.kotlin.ir.util.dumpKotlinLike 11 | 12 | class IrDumpExtension : IrGenerationExtension { 13 | 14 | var rawIr: String = "" 15 | var kotlinLikeIr: String = "" 16 | 17 | override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { 18 | rawIr = moduleFragment.dump() 19 | kotlinLikeIr = moduleFragment.dumpKotlinLike() 20 | } 21 | } 22 | 23 | @OptIn(ExperimentalCompilerApi::class) 24 | class IrDumpComponentRegistrar : CompilerPluginRegistrar() { 25 | 26 | val irDumpExtension = IrDumpExtension() 27 | 28 | override val supportsK2: Boolean 29 | get() = true 30 | 31 | override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { 32 | IrGenerationExtension.registerExtension(irDumpExtension) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/src/test/kotlin/com/bennyhuo/kotlin/kcp/deepcopy/compiler/SimpleTest.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.kcp.deepcopy.compiler 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.DeepCopyComponentRegistrar 4 | import com.tschuchort.compiletesting.KotlinCompilation 5 | import com.tschuchort.compiletesting.SourceFile 6 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 7 | import org.junit.Test 8 | import java.io.ByteArrayOutputStream 9 | import java.io.PrintStream 10 | import kotlin.test.assertEquals 11 | 12 | /** 13 | * Created by benny. 14 | */ 15 | @OptIn(ExperimentalCompilerApi::class) 16 | class SimpleTest { 17 | 18 | @Test 19 | fun irTest() { 20 | val kotlinSource = SourceFile.kotlin( 21 | "Point.kt", 22 | """ 23 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 24 | @DeepCopy 25 | data class Point(var x: Int, var y: Int) 26 | """ 27 | ) 28 | 29 | val irDumpComponentRegistrar = IrDumpComponentRegistrar() 30 | val compilation = KotlinCompilation().apply { 31 | sources = listOf(kotlinSource) 32 | componentRegistrars = listOf(DeepCopyComponentRegistrar()) 33 | compilerPluginRegistrars = listOf(irDumpComponentRegistrar) 34 | inheritClassPath = true 35 | messageOutputStream = System.out 36 | } 37 | 38 | val result = compilation.compile() 39 | 40 | assertEquals(result.exitCode, KotlinCompilation.ExitCode.OK) 41 | 42 | println(irDumpComponentRegistrar.irDumpExtension.rawIr) 43 | println(irDumpComponentRegistrar.irDumpExtension.kotlinLikeIr) 44 | } 45 | 46 | 47 | @Test 48 | fun runJvmTest() { 49 | val kotlinSource = SourceFile.kotlin( 50 | "Point.kt", 51 | """ 52 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 53 | @DeepCopy 54 | data class Point(var x: Int, var y: Int) 55 | """ 56 | ) 57 | 58 | val mainSource = SourceFile.kotlin( 59 | "Main.kt", 60 | """ 61 | import com.bennyhuo.kotlin.deepcopy.DeepCopyable 62 | 63 | fun main() { 64 | val point = Point(0, 1) 65 | val deepCopy = point.deepCopy() 66 | point.x = 1 67 | point.y = 2 68 | println(deepCopy) 69 | println(point is DeepCopyable<*>) 70 | } 71 | """ 72 | ) 73 | val compilation = KotlinCompilation().apply { 74 | sources = listOf(kotlinSource, mainSource) 75 | componentRegistrars = listOf(DeepCopyComponentRegistrar()) 76 | inheritClassPath = true 77 | messageOutputStream = System.out 78 | } 79 | 80 | val result = compilation.compile() 81 | 82 | assertEquals(result.exitCode, KotlinCompilation.ExitCode.OK) 83 | 84 | assertEquals( 85 | """ 86 | Point(x=0, y=1) 87 | true 88 | """.trimIndent(), 89 | captureStdOut { 90 | val entryClass = result.classLoader.loadClass("MainKt") 91 | val entryFunction = entryClass.getDeclaredMethod("main") 92 | entryFunction.invoke(null) 93 | } 94 | ) 95 | } 96 | 97 | 98 | private fun captureStdOut(block: () -> Unit): String { 99 | val originalStdOut = System.out 100 | val originalStdErr = System.err 101 | val stdOutStream = ByteArrayOutputStream() 102 | val printStream = PrintStream(stdOutStream) 103 | System.setOut(printStream) 104 | System.setErr(printStream) 105 | try { 106 | block() 107 | } finally { 108 | System.setOut(originalStdOut) 109 | System.setErr(originalStdErr) 110 | } 111 | return stdOutStream.toString().unify() 112 | } 113 | 114 | private fun String.unify() = replace("\r\n", "\n").trimEnd() 115 | } 116 | -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/testData/GenericsWithDeepCopyableBounds.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | // FILE: Main.kt 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | import com.bennyhuo.kotlin.deepcopy.DeepCopyable 5 | 6 | @DeepCopy 7 | data class GenericParameter>(val a: A, val b: B, val c: List, val d: List) 8 | 9 | // EXPECT 10 | // FILE: Main.kt.ir 11 | @DeepCopy 12 | data class GenericParameter> (val a: A, val b: B, val c: List, val d: List) : DeepCopyable> { 13 | fun component1(): A { 14 | return .a 15 | } 16 | fun component2(): B { 17 | return .b 18 | } 19 | fun component3(): List { 20 | return .c 21 | } 22 | fun component4(): List { 23 | return .d 24 | } 25 | fun copy(a: A = .a, b: B = .b, c: List = .c, d: List = .d): GenericParameter { 26 | return GenericParameter(a, b, c, d) 27 | } 28 | override fun toString(): String { 29 | return "GenericParameter(a=${.a}, b=${.b}, c=${.c}, d=${.d})" 30 | } 31 | override fun hashCode(): Int { 32 | var result = .a.hashCode() 33 | result = result * 31 + .b.hashCode() 34 | result = result * 31 + .c.hashCode() 35 | result = result * 31 + .d.hashCode() 36 | return result 37 | } 38 | override fun equals(other: Any?): Boolean { 39 | if ( === other) { 40 | return true 41 | } 42 | if (other) { 43 | return false 44 | } 45 | val tmp0_other_with_cast = other 46 | if (.a != tmp0_other_with_cast.a) { 47 | return false 48 | } 49 | if (.b != tmp0_other_with_cast.b) { 50 | return false 51 | } 52 | if (.c != tmp0_other_with_cast.c) { 53 | return false 54 | } 55 | if (.d != tmp0_other_with_cast.d) { 56 | return false 57 | } 58 | return true 59 | } 60 | fun deepCopy(a: A = .a, b: B = .b, c: List = .c, d: List = .d): GenericParameter { 61 | return GenericParameter(a, b.deepCopy(), c.deepCopy(), d.deepCopy()) 62 | } 63 | override fun deepCopy(): GenericParameter { 64 | return GenericParameter(.a, .b.deepCopy(), .c.deepCopy(), .d.deepCopy()) 65 | } 66 | } -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/testData/basic.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | // FILE: Main.kt [MainKt#main] 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | 5 | @DeepCopy 6 | data class DataClass(var name: String) 7 | data class PlainClass(var name: String) 8 | 9 | @DeepCopy 10 | data class Container(val dataClass: DataClass, val plainClass: PlainClass) 11 | 12 | @DeepCopy 13 | data class User(var name: String, var id: Long) { 14 | fun deepCopy(name: String = this.name, id: Long = this.id): User { 15 | return User("bennyhuo", id) 16 | } 17 | } 18 | 19 | 20 | fun main() { 21 | val container = Container(DataClass("x0"), PlainClass("y0")) 22 | val copy = container.deepCopy() 23 | 24 | println(container !== copy) 25 | println(container.dataClass !== copy.dataClass) 26 | println(container.plainClass === copy.plainClass) 27 | 28 | container.dataClass.name = "x1" 29 | container.plainClass.name = "y1" 30 | 31 | println(copy.dataClass.name == "x0") 32 | println(copy.plainClass.name == "y1") 33 | 34 | val user = User("benny", 1) 35 | println(user.deepCopy(id = 0).name == "bennyhuo") 36 | } 37 | 38 | // EXPECT 39 | // FILE: MainKt.main.stdout 40 | true 41 | true 42 | false 43 | true 44 | false 45 | true -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/testData/collectionElementCheck.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | // FILE: Main.kt [MainKt#main] 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | import com.bennyhuo.kotlin.deepcopy.DeepCopyable 5 | 6 | data class DataClass(var name: String) : DeepCopyable 7 | data class DataClass2(var name: String) 8 | 9 | @DeepCopy 10 | data class Container(val dataClasses: List, val dataClasses2: List) 11 | 12 | fun main() { 13 | val container = Container( 14 | listOf( 15 | DataClass("a"), 16 | DataClass("b"), 17 | DataClass("c"), 18 | DataClass("d"), 19 | ), 20 | listOf( 21 | DataClass2("a"), 22 | DataClass2("b"), 23 | DataClass2("c"), 24 | DataClass2("d"), 25 | ) 26 | ) 27 | 28 | val copy = container.deepCopy() 29 | println(container.dataClasses.zip(copy.dataClasses).all { (first, second) -> first === second }) 30 | println(container.dataClasses2.zip(copy.dataClasses2).all { (first, second) -> first === second }) 31 | } 32 | 33 | // EXPECT 34 | // FILE: MainKt.main.stdout 35 | false 36 | true -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/testData/declaredDeepCopy.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | // FILE: Main.kt [MainKt#main] 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | 5 | @DeepCopy 6 | data class DataClass(val name: String) { 7 | override fun deepCopy() : DataClass { 8 | return DataClass(name) 9 | } 10 | } 11 | 12 | @DeepCopy 13 | data class Container(val dataClass: DataClass, val id: Int) 14 | 15 | class PlainClass(val name: String) 16 | 17 | fun main() { 18 | val container = Container(DataClass("x"), 0) 19 | val copy = container.deepCopy() 20 | println(copy) 21 | } 22 | 23 | // EXPECT 24 | // FILE: MainKt.main.stdout 25 | Container(dataClass=DataClass(name=x), id=0) -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/testData/deepCopyForInterface.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | // FILE: Main.kt [MainKt#main] 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | import com.bennyhuo.kotlin.deepcopy.DeepCopyable 5 | 6 | class A: DeepCopyable { 7 | override fun deepCopy(): A { 8 | return A() 9 | } 10 | } 11 | 12 | data class B(val a: A): DeepCopyable { 13 | override fun deepCopy(): B { 14 | return B(a.deepCopy()) 15 | } 16 | } 17 | 18 | data class C(val a: A, val b: B): DeepCopyable 19 | 20 | @DeepCopy 21 | data class DataClass(var name: String): DeepCopyable 22 | 23 | data class PlainClass(var name: String) 24 | 25 | @DeepCopy 26 | data class Container(val dataClass: DataClass, val plainClass: PlainClass) 27 | 28 | data class User(var name: String, var age: Int): DeepCopyable 29 | 30 | fun main() { 31 | val a = A() 32 | val b = B(a) 33 | val c = C(a, b) 34 | println(a.deepCopy() !== a) 35 | println(b.deepCopy().a !== a) 36 | println(c.deepCopy().b !== b) 37 | 38 | val container = Container(DataClass("x0"), PlainClass("y0")) 39 | println(container is DeepCopyable<*>) 40 | println(container.dataClass is DeepCopyable<*>) 41 | println(container.plainClass as Any !is DeepCopyable<*>) 42 | 43 | val copy = container.deepCopy() 44 | 45 | println(container !== copy) 46 | println(container.dataClass !== copy.dataClass) 47 | // call plainClass.copy() in Container.deepCopy, this will be false 48 | println(container.plainClass === copy.plainClass) 49 | 50 | container.dataClass.name = "x1" 51 | container.plainClass.name = "y1" 52 | 53 | println(copy.dataClass.name == "x0") 54 | println(copy.plainClass.name == "y1") 55 | 56 | val user = User("bennyhuo", 10) 57 | val userCopy = user.deepCopy() 58 | println(userCopy) 59 | println(user === userCopy) 60 | } 61 | 62 | // EXPECT 63 | // FILE: MainKt.main.stdout 64 | true 65 | true 66 | true 67 | true 68 | true 69 | true 70 | true 71 | true 72 | false 73 | true 74 | false 75 | User(name=bennyhuo, age=10) 76 | false -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/testData/modules.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | // MODULE: lib-user 3 | // FILE: User.kt 4 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 5 | 6 | @DeepCopy 7 | data class User(var name: String) 8 | 9 | // MODULE: lib-project / lib-user 10 | // FILE: User.kt 11 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 12 | 13 | @DeepCopy 14 | data class Project(var name: String, var owner: User) 15 | 16 | // MODULE: lib-country 17 | // FILE: Country.kt 18 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 19 | 20 | @DeepCopy 21 | data class Country(var name: String) 22 | 23 | // MODULE: main / lib-project, lib-country 24 | // FILE: Main.kt [MainKt#main] 25 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 26 | 27 | @DeepCopy 28 | data class Company( 29 | val name: String, 30 | val country: Country, 31 | val projects: List 32 | ) 33 | 34 | fun main() { 35 | val company = Company( 36 | "JetBrains", 37 | Country("Czech"), 38 | listOf( 39 | Project("IntelliJ", User("JetBrains")), 40 | Project("Kotlin", User("JetBrains")) 41 | ) 42 | ) 43 | 44 | val company2 = company.deepCopy() 45 | println(company) 46 | println(company2) 47 | 48 | company2.country.name = "Czech Republic" 49 | 50 | println(company) 51 | println(company2) 52 | 53 | println(company.projects === company2.projects) 54 | } 55 | // EXPECT 56 | // MODULE: main 57 | // FILE: MainKt.main.stdout 58 | Company(name=JetBrains, country=Country(name=Czech), projects=[Project(name=IntelliJ, owner=User(name=JetBrains)), Project(name=Kotlin, owner=User(name=JetBrains))]) 59 | Company(name=JetBrains, country=Country(name=Czech), projects=[Project(name=IntelliJ, owner=User(name=JetBrains)), Project(name=Kotlin, owner=User(name=JetBrains))]) 60 | Company(name=JetBrains, country=Country(name=Czech), projects=[Project(name=IntelliJ, owner=User(name=JetBrains)), Project(name=Kotlin, owner=User(name=JetBrains))]) 61 | Company(name=JetBrains, country=Country(name=Czech Republic), projects=[Project(name=IntelliJ, owner=User(name=JetBrains)), Project(name=Kotlin, owner=User(name=JetBrains))]) 62 | false -------------------------------------------------------------------------------- /kcp-impl/compiler-kcp/testData/simple.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | // FILE: Main.kt [MainKt#main] 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | import com.bennyhuo.kotlin.deepcopy.DeepCopyable 5 | 6 | @DeepCopy 7 | data class DataClass(var value: String) 8 | 9 | fun main() { 10 | val dataClass = DataClass("hello") 11 | println(dataClass.deepCopy("world").value == "world") 12 | println(dataClass is DeepCopyable<*>) 13 | } 14 | 15 | // EXPECT 16 | // FILE: MainKt.main.stdout 17 | true 18 | true 19 | // FILE: Main.kt.ir 20 | @DeepCopy 21 | data class DataClass(var value: String) : DeepCopyable { 22 | fun component1(): String { 23 | return .value 24 | } 25 | fun copy(value: String = .value): DataClass { 26 | return DataClass(value) 27 | } 28 | override fun toString(): String { 29 | return "DataClass(value=${.value})" 30 | } 31 | override fun hashCode(): Int { 32 | return .value.hashCode() 33 | } 34 | override fun equals(other: Any?): Boolean { 35 | if ( === other) { 36 | return true 37 | } 38 | if (other) { 39 | return false 40 | } 41 | val tmp0_other_with_cast = other 42 | if (.value != tmp0_other_with_cast.value) { 43 | return false 44 | } 45 | return true 46 | } 47 | fun deepCopy(value: String = .value): DataClass { 48 | return DataClass(value) 49 | } 50 | override fun deepCopy(): DataClass { 51 | return DataClass(.value) 52 | } 53 | } 54 | fun main() { 55 | val dataClass = DataClass("hello") 56 | println(dataClass.deepCopy("world").value == "world") 57 | println(dataClass) 58 | } -------------------------------------------------------------------------------- /kcp-impl/gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=com.bennyhuo.kotlin 2 | 3 | KOTLIN_PLUGIN_ID=com.bennyhuo.kotlin.plugin.deepcopy 4 | -------------------------------------------------------------------------------- /kcp-impl/plugin-gradle/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-gradle-plugin") 3 | kotlin("jvm") 4 | id("com.github.gmazzo.buildconfig") 5 | } 6 | 7 | dependencies { 8 | implementation(kotlin("gradle-plugin-api")) 9 | implementation(kotlin("stdlib")) 10 | } 11 | 12 | buildConfig { 13 | val compilerPluginProject = project(":kcp-impl:compiler-kcp-embeddable") 14 | packageName("${compilerPluginProject.group}.kcp") 15 | buildConfigField("String", "KOTLIN_PLUGIN_ID", "\"${property("KOTLIN_PLUGIN_ID")}\"") 16 | buildConfigField("String", "KOTLIN_PLUGIN_GROUP", "\"${compilerPluginProject.group}\"") 17 | buildConfigField("String", "KOTLIN_PLUGIN_NAME", "\"${compilerPluginProject.property("POM_ARTIFACT_ID")}\"") 18 | buildConfigField("String", "KOTLIN_PLUGIN_VERSION", "\"${compilerPluginProject.version}\"") 19 | } 20 | 21 | gradlePlugin { 22 | plugins { 23 | create("DeepCopyGradlePlugin") { 24 | id = project.properties["KOTLIN_PLUGIN_ID"] as String 25 | displayName = "Kotlin DeepCopy plugin for data class" 26 | description = "Kotlin DeepCopy plugin for data class" 27 | implementationClass = "com.bennyhuo.kotlin.deepcopy.gradle.DeepCopyGradlePlugin" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /kcp-impl/plugin-gradle/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=deepcopy-gradle-plugin-kcp 2 | POM_NAME=KotlinDeepCopy-Gradle-Plugin-Kcp -------------------------------------------------------------------------------- /kcp-impl/plugin-gradle/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/gradle/DeepCopyGradlePlugin.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.gradle 2 | 3 | import com.bennyhuo.kotlin.kcp.BuildConfig 4 | import org.gradle.api.Project 5 | import org.gradle.api.provider.Provider 6 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation 7 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin 8 | import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact 9 | import org.jetbrains.kotlin.gradle.plugin.SubpluginOption 10 | 11 | class DeepCopyGradlePlugin : KotlinCompilerPluginSupportPlugin { 12 | override fun apply(target: Project) = Unit 13 | 14 | override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true 15 | 16 | override fun getCompilerPluginId(): String = BuildConfig.KOTLIN_PLUGIN_ID 17 | 18 | override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact( 19 | groupId = BuildConfig.KOTLIN_PLUGIN_GROUP, 20 | artifactId = BuildConfig.KOTLIN_PLUGIN_NAME, 21 | version = BuildConfig.KOTLIN_PLUGIN_VERSION 22 | ) 23 | 24 | // override fun getPluginArtifactForNative(): SubpluginArtifact = SubpluginArtifact( 25 | // groupId = BuildConfig.KOTLIN_PLUGIN_GROUP, 26 | // artifactId = BuildConfig.KOTLIN_PLUGIN_NAME + "-native", 27 | // version = BuildConfig.KOTLIN_PLUGIN_VERSION 28 | // ) 29 | 30 | override fun applyToCompilation( 31 | kotlinCompilation: KotlinCompilation<*> 32 | ): Provider> { 33 | val project = kotlinCompilation.target.project 34 | return project.provider { emptyList() } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.intellij.tasks.PublishPluginTask 2 | 3 | plugins { 4 | kotlin("jvm") 5 | id("org.jetbrains.intellij") version("1.16.0") 6 | } 7 | 8 | dependencies { 9 | implementation(project(":kcp-impl:compiler-kcp")) 10 | } 11 | 12 | intellij { 13 | version.set("2023.2.5") 14 | plugins.set(listOf("org.jetbrains.kotlin:232-1.9.20-release-507-IJ10072.27", "com.intellij.gradle:232.10227.11")) 15 | pluginName.set("DeepCopy") 16 | updateSinceUntilBuild.set(false) 17 | } 18 | 19 | tasks { 20 | withType { 21 | project.findProperty("intellij.token")?.let { 22 | token.set(it.toString()) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/gradle.properties: -------------------------------------------------------------------------------- 1 | shouldPublish=false -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/DeepCopyAvailability.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.extensions.ExtensionPointName 5 | import com.intellij.openapi.module.Module 6 | import com.intellij.psi.PsiElement 7 | import org.jetbrains.kotlin.idea.compilerPlugin.parcelize.ParcelizeAvailabilityProvider 8 | import org.jetbrains.kotlin.idea.util.module 9 | 10 | /** 11 | * Created by benny at 2022/1/13 1:04 PM. 12 | */ 13 | object DeepCopyAvailability { 14 | fun isAvailable(element: PsiElement): Boolean { 15 | if (ApplicationManager.getApplication().isUnitTestMode) { 16 | return true 17 | } 18 | 19 | val module = element.module ?: return false 20 | return isAvailable(module) 21 | } 22 | 23 | fun isAvailable(module: Module): Boolean { 24 | if (ApplicationManager.getApplication().isUnitTestMode) { 25 | return true 26 | } 27 | 28 | return DeepCopyAvailabilityProvider.PROVIDER_EP.getExtensions(module.project).any { it.isAvailable(module) } 29 | } 30 | } 31 | 32 | interface DeepCopyAvailabilityProvider { 33 | companion object { 34 | val PROVIDER_EP: ExtensionPointName = 35 | ExtensionPointName("com.bennyhuo.kotlin.deepcopy.ide.availabilityProvider") 36 | } 37 | 38 | fun isAvailable(module: Module): Boolean 39 | } -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/DeepCopyModelBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide 2 | 3 | import com.bennyhuo.kotlin.kcp.BuildConfig 4 | import org.gradle.api.Project 5 | import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder 6 | import org.jetbrains.plugins.gradle.tooling.ModelBuilderService 7 | import java.io.Serializable 8 | 9 | /** 10 | * Created by benny at 2022/1/13 8:14 AM. 11 | */ 12 | class DeepCopyModelBuilder : ModelBuilderService { 13 | override fun buildAll(modelName: String?, project: Project): Any { 14 | val deepCopyPlugin = project.plugins.findPlugin(BuildConfig.KOTLIN_PLUGIN_ID) 15 | return DeepCopyGradleModelImpl(deepCopyPlugin != null) 16 | } 17 | 18 | override fun canBuild(modelName: String?): Boolean { 19 | return modelName == DeepCopyGradleModel::class.java.name 20 | } 21 | 22 | override fun getErrorMessageBuilder(project: Project, e: Exception): ErrorMessageBuilder { 23 | return ErrorMessageBuilder.create(project, e, "Gradle import errors") 24 | .withDescription("Unable to build ${BuildConfig.KOTLIN_PLUGIN_ID} plugin configuration") 25 | } 26 | } 27 | 28 | interface DeepCopyGradleModel : Serializable { 29 | val isEnabled: Boolean 30 | } 31 | 32 | class DeepCopyGradleModelImpl(override val isEnabled: Boolean) : DeepCopyGradleModel 33 | -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/DeepCopyProjectResolverExtension.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide 2 | 3 | import com.intellij.openapi.externalSystem.model.DataNode 4 | import com.intellij.openapi.externalSystem.model.Key 5 | import com.intellij.openapi.externalSystem.model.ProjectKeys 6 | import com.intellij.openapi.externalSystem.model.project.AbstractExternalEntityData 7 | import com.intellij.openapi.externalSystem.model.project.ModuleData 8 | import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService 9 | import com.intellij.serialization.PropertyMapping 10 | import org.gradle.tooling.model.idea.IdeaModule 11 | import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension 12 | import org.jetbrains.plugins.gradle.util.GradleConstants 13 | 14 | /** 15 | * Created by benny at 2022/1/13 8:13 AM. 16 | */ 17 | class DeepCopyIdeModel @PropertyMapping("isEnabled") constructor( 18 | val isEnabled: Boolean 19 | ) : AbstractExternalEntityData(GradleConstants.SYSTEM_ID) { 20 | companion object { 21 | val KEY = Key.create(DeepCopyIdeModel::class.java, ProjectKeys.CONTENT_ROOT.processingWeight + 1) 22 | } 23 | } 24 | 25 | class DeepCopyIdeModelDataService : AbstractProjectDataService() { 26 | override fun getTargetDataKey() = DeepCopyIdeModel.KEY 27 | } 28 | 29 | class DeepCopyProjectResolverExtension : AbstractProjectResolverExtension() { 30 | 31 | override fun getExtraProjectModelClasses() = setOf(DeepCopyGradleModel::class.java) 32 | override fun getToolingExtensionsClasses() = setOf(DeepCopyModelBuilder::class.java, Unit::class.java) 33 | 34 | override fun populateModuleExtraModels(gradleModule: IdeaModule, ideModule: DataNode) { 35 | val deepCopyGradleModel = resolverCtx.getExtraProject(gradleModule, DeepCopyGradleModel::class.java) 36 | 37 | if (deepCopyGradleModel != null && deepCopyGradleModel.isEnabled) { 38 | ideModule.createChild(DeepCopyIdeModel.KEY, DeepCopyIdeModel(isEnabled = deepCopyGradleModel.isEnabled)) 39 | } 40 | 41 | super.populateModuleExtraModels(gradleModule, ideModule) 42 | } 43 | } -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/GradleDeepCopyAvailabilityProvider.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide 2 | 3 | import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil 4 | import com.intellij.openapi.externalSystem.util.ExternalSystemUtil 5 | import com.intellij.openapi.module.Module 6 | import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil 7 | import org.jetbrains.plugins.gradle.util.GradleConstants 8 | 9 | class GradleDeepCopyAvailabilityProvider : DeepCopyAvailabilityProvider { 10 | override fun isAvailable(module: Module): Boolean { 11 | val path = ExternalSystemApiUtil.getExternalProjectPath(module) ?: return false 12 | val externalProjectInfo = ExternalSystemUtil.getExternalProjectInfo(module.project, GradleConstants.SYSTEM_ID, path) ?: return false 13 | val moduleData = GradleProjectResolverUtil.findModule(externalProjectInfo.externalProjectStructure, path) ?: return false 14 | return ExternalSystemApiUtil.find(moduleData, DeepCopyIdeModel.KEY)?.data?.isEnabled ?: false 15 | } 16 | } -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/IdeDeepCopyComponentContainerContributor.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.checker.DeepCopyComponentContainerContributor 4 | import org.jetbrains.kotlin.analyzer.moduleInfo 5 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 6 | import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo.ModuleSourceInfo 7 | 8 | class IdeDeepCopyComponentContainerContributor : DeepCopyComponentContainerContributor() { 9 | override fun ModuleDescriptor.isDeepCopyPluginEnabled(): Boolean { 10 | return moduleInfo.safeAs() 11 | ?.module?.let(DeepCopyAvailability::isAvailable) 12 | ?: false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/IdeDeepCopyIrGenerationExtension.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.ir.DeepCopyIrGenerationExtension 4 | import com.intellij.psi.PsiElement 5 | 6 | /** 7 | * Created by benny at 2022/1/14 12:12 AM. 8 | */ 9 | class IdeDeepCopyIrGenerationExtension : DeepCopyIrGenerationExtension() { 10 | override fun PsiElement.isDeepCopyPluginEnabled(): Boolean { 11 | return DeepCopyAvailability.isAvailable(this) 12 | } 13 | } -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/IdeDeepCopyResolveExtension.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.symbol.DeepCopyResolveExtension 4 | import com.intellij.psi.PsiElement 5 | 6 | /** 7 | * Created by benny at 2022/1/13 5:09 PM. 8 | */ 9 | class IdeDeepCopyResolveExtension: DeepCopyResolveExtension() { 10 | 11 | override fun PsiElement.isDeepCopyPluginEnabled(): Boolean { 12 | return DeepCopyAvailability.isAvailable(this) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/KotlinDeepCopyBundle.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide 2 | 3 | import org.jetbrains.annotations.Nls 4 | import org.jetbrains.annotations.NonNls 5 | import org.jetbrains.annotations.PropertyKey 6 | import org.jetbrains.kotlin.util.AbstractKotlinBundle 7 | 8 | @NonNls 9 | private const val BUNDLE = "messages.KotlinDeepCopyBundle" 10 | 11 | object KotlinDeepCopyBundle : AbstractKotlinBundle(BUNDLE) { 12 | @Nls 13 | @JvmStatic 14 | fun message(@NonNls @PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): String = 15 | @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") 16 | getMessage(key, *params) 17 | } 18 | -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide 2 | 3 | import org.jetbrains.kotlin.idea.core.ShortenReferences 4 | import org.jetbrains.kotlin.psi.KtClass 5 | import org.jetbrains.kotlin.psi.KtElement 6 | 7 | /** 8 | * Created by benny at 2022/1/15 3:55 PM. 9 | */ 10 | fun T.shortenReferences() = ShortenReferences.DEFAULT.process(this) 11 | 12 | fun KtClass.isDataClassLike() = hasPrimaryConstructor() && primaryConstructorParameters.let { 13 | it.isNotEmpty() && it.all { it.hasValOrVar() } 14 | } 15 | 16 | inline fun Any?.safeAs(): T? = this as? T 17 | -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/quickfix/DeepCopyAddSupertypeQuickFix.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide.quickfix 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.DEEP_COPY_INTERFACE_NAME 4 | import com.bennyhuo.kotlin.deepcopy.ide.KotlinDeepCopyBundle 5 | import com.bennyhuo.kotlin.deepcopy.ide.shortenReferences 6 | import com.intellij.openapi.editor.Editor 7 | import com.intellij.openapi.project.Project 8 | import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction 9 | import org.jetbrains.kotlin.psi.KtClass 10 | import org.jetbrains.kotlin.psi.KtFile 11 | import org.jetbrains.kotlin.psi.KtPsiFactory 12 | 13 | class DeepCopyAddSupertypeQuickFix(private val ktClass: KtClass) : KotlinQuickFixAction(ktClass) { 14 | 15 | override fun getFamilyName() = text 16 | 17 | override fun getText() = KotlinDeepCopyBundle.message( 18 | "deepcopy.fix.add.deepcopyable.supertype", 19 | ktClass.fqName!!.asString() 20 | ) 21 | 22 | override fun invoke(project: Project, editor: Editor?, file: KtFile) { 23 | val ktPsiFactory = KtPsiFactory(project, markGenerated = true) 24 | val supertypeName = "$DEEP_COPY_INTERFACE_NAME<${ktClass.fqName}>" 25 | ktClass.addSuperTypeListEntry(ktPsiFactory.createSuperTypeEntry(supertypeName)).shortenReferences() 26 | } 27 | } -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/quickfix/DeepCopyAnnotationQuickFix.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide.quickfix 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.DEEP_COPY_ANNOTATION_NAME 4 | import com.bennyhuo.kotlin.deepcopy.ide.KotlinDeepCopyBundle 5 | import com.intellij.openapi.editor.Editor 6 | import com.intellij.openapi.project.Project 7 | import org.jetbrains.kotlin.idea.base.utils.fqname.getKotlinFqName 8 | import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction 9 | import org.jetbrains.kotlin.idea.util.addAnnotation 10 | import org.jetbrains.kotlin.lexer.KtModifierKeywordToken 11 | import org.jetbrains.kotlin.name.FqName 12 | import org.jetbrains.kotlin.psi.KtClass 13 | import org.jetbrains.kotlin.psi.KtFile 14 | 15 | class DeepCopyAnnotationQuickFix(private val ktClass: KtClass) : 16 | KotlinQuickFixAction(ktClass) { 17 | 18 | override fun getFamilyName() = text 19 | 20 | override fun getText(): String { 21 | return if (ktClass.isData()) { 22 | KotlinDeepCopyBundle.message("deepcopy.fix.add.annotate", ktClass.getKotlinFqName()!!.asString()) 23 | } else { 24 | KotlinDeepCopyBundle.message("deepcopy.fix.dataclass", ktClass.getKotlinFqName()!!.asString()) 25 | } 26 | } 27 | 28 | override fun invoke(project: Project, editor: Editor?, file: KtFile) { 29 | if (!ktClass.isData()) { 30 | ktClass.addModifier(KtModifierKeywordToken.keywordModifier("data")) 31 | } 32 | ktClass.addAnnotation(FqName(DEEP_COPY_ANNOTATION_NAME)) 33 | } 34 | } -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/quickfix/DeepCopyQuickFixContributor.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide.quickfix 2 | 3 | import com.bennyhuo.kotlin.deepcopy.compiler.kcp.checker.ErrorsDeepCopy 4 | import org.jetbrains.kotlin.idea.quickfix.QuickFixContributor 5 | import org.jetbrains.kotlin.idea.quickfix.QuickFixes 6 | 7 | class DeepCopyQuickFixContributor : QuickFixContributor { 8 | override fun registerQuickFixes(quickFixes: QuickFixes) { 9 | quickFixes.register( 10 | ErrorsDeepCopy.TYPE_NOT_IMPLEMENT_DEEPCOPYABLE, 11 | DeepCopyQuickFixFactory, 12 | ) 13 | } 14 | } -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/ide/quickfix/DeepCopyQuickFixFactory.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.ide.quickfix 2 | 3 | import com.bennyhuo.kotlin.deepcopy.ide.isDataClassLike 4 | import com.bennyhuo.kotlin.deepcopy.ide.safeAs 5 | import com.intellij.codeInsight.intention.IntentionAction 6 | import org.jetbrains.kotlin.diagnostics.Diagnostic 7 | import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory 8 | import org.jetbrains.kotlin.idea.references.mainReference 9 | import org.jetbrains.kotlin.nj2k.postProcessing.resolve 10 | import org.jetbrains.kotlin.psi.KtClass 11 | import org.jetbrains.kotlin.psi.KtUserType 12 | 13 | /** 14 | * Created by benny. 15 | */ 16 | object DeepCopyQuickFixFactory : KotlinSingleIntentionActionFactory() { 17 | override fun createAction(diagnostic: Diagnostic): IntentionAction? { 18 | val ktClass = diagnostic.psiElement.safeAs() 19 | ?.referenceExpression?.mainReference?.resolve()?.safeAs() 20 | ?.takeIf { it.containingFile.isWritable } ?: return null 21 | 22 | return if (ktClass.isData() || ktClass.isDataClassLike()) { 23 | DeepCopyAnnotationQuickFix(ktClass) 24 | } else { 25 | DeepCopyAddSupertypeQuickFix(ktClass) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.bennyhuo.kotlin.deepcopy 3 | DeepCopy for Kotlin Data Class 4 | 1.9.20-1.0.1 5 | Bennyhuo 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | com.intellij.modules.platform 17 | org.jetbrains.kotlin 18 | com.intellij.gradle 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService: -------------------------------------------------------------------------------- 1 | com.bennyhuo.kotlin.deepcopy.ide.DeepCopyModelBuilder -------------------------------------------------------------------------------- /kcp-impl/plugin-ide/src/main/resources/messages/KotlinDeepCopyBundle.properties: -------------------------------------------------------------------------------- 1 | deepcopy.fix.add.deepcopyable.supertype=Implement ''DeepCopyable'' for {0} 2 | deepcopy.fix.add.annotate=Annotate {0} with ''@DeepCopy'' 3 | deepcopy.fix.dataclass=Make {0} data class and annotate it with ''@DeepCopy'' -------------------------------------------------------------------------------- /kcp-impl/sample/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.jetbrains.kotlin.jvm' version "1.9.20" 4 | id "com.bennyhuo.kotlin.plugin.deepcopy" version "1.9.20.0" 5 | } 6 | 7 | dependencies { 8 | implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.20") 9 | implementation("com.bennyhuo.kotlin:deepcopy-runtime:1.9.20.0") 10 | } 11 | 12 | compileKotlin { 13 | kotlinOptions.jvmTarget = "1.8" 14 | } 15 | compileTestKotlin { 16 | kotlinOptions.jvmTarget = "1.8" 17 | } 18 | -------------------------------------------------------------------------------- /kcp-impl/sample/gradle.properties: -------------------------------------------------------------------------------- 1 | # kotlin.compiler.execution.strategy=in-process 2 | -------------------------------------------------------------------------------- /kcp-impl/sample/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /kcp-impl/sample/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /kcp-impl/sample/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | 8 | resolutionStrategy { 9 | eachPlugin { 10 | switch (requested.id.id) { 11 | case "com.bennyhuo.kotlin.plugin.deepcopy": { 12 | useModule("com.bennyhuo.kotlin:deepcopy-gradle-plugin-kcp:1.9.20-1.0.0") 13 | break 14 | } 15 | } 16 | } 17 | } 18 | } 19 | 20 | dependencyResolutionManagement { 21 | repositories { 22 | mavenLocal() 23 | mavenCentral() 24 | } 25 | } 26 | 27 | includeBuild("../../") { 28 | dependencySubstitution { 29 | substitute(module("com.bennyhuo.kotlin:deepcopy-gradle-plugin-kcp")).using(project(":kcp-impl:plugin-gradle")) 30 | substitute(module("com.bennyhuo.kotlin:deepcopy-compiler-kcp-embeddable")).using(project(":kcp-impl:compiler-kcp-embeddable")) 31 | substitute(module("com.bennyhuo.kotlin:deepcopy-compiler-kcp-embeddable")).using(project(":kcp-impl:compiler-kcp-embeddable")) 32 | substitute(module("com.bennyhuo.kotlin:deepcopy-runtime")).using(project(":runtime")) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /kcp-impl/sample/src/main/java/com/bennyhuo/kotlin/deepcopy/sample/Hello.java: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample; 2 | 3 | /** 4 | * Created by benny. 5 | */ 6 | public class Hello { 7 | } 8 | -------------------------------------------------------------------------------- /kcp-impl/sample/src/main/java/com/bennyhuo/kotlin/deepcopy/sample/QuickFix.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | 5 | class UserInfo(var name: String, var age: Int, var bio: String) 6 | data class User(var id: Long, var info: UserInfo) 7 | 8 | @DeepCopy 9 | data class Project(var name: String, var owner: User) -------------------------------------------------------------------------------- /kcp-impl/sample/src/main/java/com/bennyhuo/kotlin/deepcopy/sample/Sample.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample 2 | 3 | import com.bennyhuo.kotlin.deepcopy.DeepCopyable 4 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 5 | 6 | @DeepCopy 7 | data class District(var name: String) 8 | 9 | @DeepCopy 10 | data class Location(var lat: Double, var lng: Double) 11 | 12 | @DeepCopy 13 | data class Company(var name: String, var location: Location, var district: District) 14 | 15 | @DeepCopy 16 | data class Speaker(var name: String, var age: Int, var company: Company) 17 | 18 | @DeepCopy 19 | data class Talk(var name: String, var speaker: Speaker) { 20 | fun deepCopy(name: String = this.name, speaker: Speaker = this.speaker): Talk { 21 | return Talk(name, speaker) 22 | } 23 | } 24 | 25 | class A : DeepCopyable { 26 | override fun deepCopy(): A { 27 | return A() 28 | } 29 | } 30 | 31 | @DeepCopy 32 | data class AA(val a: A) : DeepCopyable { 33 | override fun deepCopy(): AA { 34 | return AA(a) 35 | } 36 | } 37 | 38 | data class B(val name: String) 39 | 40 | data class DataClass(var name: String) : DeepCopyable 41 | 42 | data class DataClass2(var name: String) 43 | 44 | @DeepCopy 45 | data class Container(val dataClasses: List, val dataClasses2: List) 46 | 47 | 48 | typealias NNN = Number 49 | data class X(val a: Int, val b: Double?, val c: String, val d: NNN): DeepCopyable 50 | 51 | fun main(args: Array) { 52 | val talk = Talk( 53 | "如何优雅地使用数据类", 54 | Speaker( 55 | "bennyhuo 不是算命的", 56 | 1, 57 | Company( 58 | "猿辅导", 59 | Location(39.9, 116.3), 60 | District("北京郊区") 61 | ) 62 | ) 63 | ) 64 | 65 | val a = A() 66 | val b = a.deepCopy() 67 | 68 | println(a === b) 69 | 70 | val aa = AA(a) 71 | val bb = aa.deepCopy() 72 | 73 | println(aa === bb) 74 | println(aa.a === bb.a) 75 | 76 | val copiedTalk = talk.deepCopy() 77 | copiedTalk.name = "Kotlin 编译器插件:我们不期待" 78 | copiedTalk.speaker.company = Company( 79 | "猿辅导", 80 | Location(39.9, 116.3), 81 | District("华鼎世家对面") 82 | ) 83 | println(talk === copiedTalk) 84 | println(talk.speaker === copiedTalk.speaker) 85 | 86 | println(talk is DeepCopyable<*>) 87 | println(B("Hello") as Any !is DeepCopyable<*>) 88 | 89 | val container = Container( 90 | listOf( 91 | DataClass("a"), 92 | DataClass("b"), 93 | DataClass("c"), 94 | DataClass("d"), 95 | ), 96 | listOf( 97 | DataClass2("a"), 98 | DataClass2("b"), 99 | DataClass2("c"), 100 | DataClass2("d"), 101 | ) 102 | ) 103 | 104 | val copy = container.deepCopy() 105 | println(container.dataClasses.zip(copy.dataClasses).all { (first, second) -> first != second }) 106 | println(container.dataClasses2.zip(copy.dataClasses2).all { (first, second) -> first == second }) 107 | } -------------------------------------------------------------------------------- /reflect-impl-js/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.jetbrains.kotlin.js") 3 | } 4 | 5 | dependencies { 6 | implementation(kotlin("stdlib-js")) 7 | 8 | testImplementation(kotlin("test-js")) 9 | } 10 | 11 | kotlin { 12 | js(IR) { 13 | moduleName = "deepcopy-reflect-js" 14 | binaries.library() 15 | nodejs() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /reflect-impl-js/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=deepcopy-reflect-js 2 | POM_NAME=KotlinDeepCopy-Reflect-Js 3 | GROUP=com.bennyhuo.kotlin -------------------------------------------------------------------------------- /reflect-impl-js/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/DeepCopy.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy 2 | 3 | import kotlin.math.abs 4 | 5 | /** 6 | * Created by benny. 7 | */ 8 | interface DeepCopyable 9 | 10 | fun T.deepCopy(): T { 11 | val constructor = this::class.js.asDynamic() 12 | val parameters = (1..Int.MAX_VALUE).asSequence().map { 13 | componentFunction(constructor.prototype, "component${it}") 14 | }.takeWhile { 15 | it !== undefined 16 | }.map { 17 | it.call(this).unsafeCast() 18 | .let { 19 | (it as? DeepCopyable)?.deepCopy() ?: it 20 | } 21 | }.toList().toTypedArray() 22 | 23 | val newInstance = js("{}") 24 | newInstance.__proto__ = constructor.prototype 25 | constructor.apply(newInstance, parameters) 26 | return newInstance as T 27 | } 28 | 29 | private fun componentFunction( 30 | prototype: dynamic, name: String 31 | ) = prototype[name] 32 | ?: prototype["${name}_${abs(name.hashCode()).toString(36)}_k$"] 33 | ?: prototype["${name}_0_k$"] -------------------------------------------------------------------------------- /reflect-impl-js/src/test/kotlin/com/bennyhuo/kotlin/deepcopy/DeepCopyTest.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | import kotlin.test.assertNotSame 6 | 7 | /** 8 | * Created by benny at 2021/6/26 8:36. 9 | */ 10 | data class Point(var x: Int, var y: Int) : DeepCopyable 11 | 12 | data class Text( 13 | var id: Long, 14 | var text: String, 15 | var point: Point 16 | ) : DeepCopyable 17 | 18 | class DeepCopyTest { 19 | 20 | @Test 21 | fun basic() { 22 | val text = Text(0, "Kotlin", Point(10, 20)) 23 | val newText = text.deepCopy().apply { id = 2 } 24 | 25 | assertNotSame(text, newText) 26 | assertNotSame(text.point, newText.point) 27 | 28 | newText.point.x = 100 29 | 30 | assertEquals(text.point.x, 10) 31 | assertEquals(newText.point.x, 100) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /reflect-impl/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.jetbrains.kotlin.jvm' 4 | } 5 | 6 | sourceCompatibility = 1.8 7 | 8 | dependencies { 9 | implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") 10 | implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") 11 | testImplementation group: 'junit', name: 'junit', version: '4.12' 12 | } 13 | 14 | compileKotlin { 15 | kotlinOptions.jvmTarget = "1.8" 16 | } 17 | compileTestKotlin { 18 | kotlinOptions.jvmTarget = "1.8" 19 | } -------------------------------------------------------------------------------- /reflect-impl/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=deepcopy-reflect 2 | POM_NAME=KotlinDeepCopy-Reflect 3 | GROUP=com.bennyhuo.kotlin -------------------------------------------------------------------------------- /reflect-impl/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/reflect/DeepCopy.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.reflect 2 | 3 | import kotlin.reflect.KClass 4 | import kotlin.reflect.full.declaredMemberProperties 5 | import kotlin.reflect.full.primaryConstructor 6 | 7 | interface DeepCopyable 8 | 9 | fun T.deepCopy(): T { 10 | if (!this::class.isData) return this 11 | 12 | val thisClass = (this::class as KClass) 13 | return thisClass.primaryConstructor!!.let { primaryConstructor -> 14 | primaryConstructor.parameters.associateWith { parameter -> 15 | thisClass.declaredMemberProperties 16 | .first { it.name == parameter.name } 17 | .get(this) 18 | ?.let { 19 | (it as? DeepCopyable)?.deepCopy() ?: it 20 | } 21 | }.let(primaryConstructor::callBy) 22 | } 23 | } -------------------------------------------------------------------------------- /reflect-impl/src/test/kotlin/com/bennyhuo/kotlin/deepcopy/compiler/DeepCopyTest.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.compiler 2 | 3 | import com.bennyhuo.kotlin.deepcopy.reflect.DeepCopyable 4 | import com.bennyhuo.kotlin.deepcopy.reflect.deepCopy 5 | import org.junit.Test 6 | 7 | data class Location(var lat: Double, var lng: Double) 8 | 9 | data class Company(var name: String, var location: Location) : DeepCopyable 10 | 11 | data class Speaker(var name: String, var age: Int, var company: Company) : DeepCopyable 12 | 13 | data class Talk(var name: String, var speaker: Speaker) : DeepCopyable 14 | 15 | 16 | data class Point(var x: Int, var y: Int): DeepCopyable 17 | data class Text(var id: Long, var text: String, var point: Point): DeepCopyable 18 | 19 | class DeepCopyTest { 20 | 21 | @Test 22 | fun test0() { 23 | val text = Text(0, "Kotlin", Point(10, 20)) 24 | val newText = text.copy(1) 25 | newText.point.x = 100 26 | println(text) 27 | 28 | val newText2 = text.deepCopy().apply { id = 2 } 29 | newText2.point.x = 200 30 | println(text) 31 | } 32 | 33 | @Test 34 | fun test() { 35 | val talk = Talk( 36 | "如何优雅地使用数据类", 37 | Speaker( 38 | "bennyhuo 不是算命的", 39 | 1, 40 | Company( 41 | "猿辅导", 42 | Location(39.9, 116.3) 43 | ) 44 | ) 45 | ) 46 | 47 | val copiedTalk = talk.deepCopy() 48 | 49 | assert(talk.speaker !== copiedTalk.speaker) 50 | assert(talk.speaker.company !== copiedTalk.speaker.company) 51 | assert(talk.speaker.company.location === copiedTalk.speaker.company.location) 52 | } 53 | } -------------------------------------------------------------------------------- /runtime/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'org.jetbrains.kotlin.jvm' 4 | } 5 | 6 | sourceCompatibility = 1.8 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 14 | api project(":annotations") 15 | testApi group: 'junit', name: 'junit', version: '4.12' 16 | } 17 | 18 | compileKotlin { 19 | kotlinOptions.jvmTarget = "1.8" 20 | } 21 | compileTestKotlin { 22 | kotlinOptions.jvmTarget = "1.8" 23 | } -------------------------------------------------------------------------------- /runtime/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=deepcopy-runtime 2 | POM_NAME=KotlinDeepCopy-Runtime 3 | 4 | GROUP=com.bennyhuo.kotlin 5 | -------------------------------------------------------------------------------- /runtime/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/CollectionSupport.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY") 2 | package com.bennyhuo.kotlin.deepcopy 3 | 4 | /** 5 | * Created by benny at 2022/1/12 8:49 PM. 6 | */ 7 | private fun deepCopyMapper(value: T): T { 8 | return if (value is DeepCopyable<*>) { 9 | value.deepCopy() as T 10 | } else { 11 | when(value) { 12 | is Set<*> -> value.deepCopy() 13 | is List<*> -> value.deepCopy() 14 | is Collection<*> -> value.deepCopy() 15 | is Iterable<*> -> value.deepCopy() 16 | else -> value 17 | } as T 18 | } 19 | } 20 | 21 | fun Iterable.deepCopy(): MutableIterable = mapTo(ArrayList(), ::deepCopyMapper) 22 | 23 | fun Collection.deepCopy(): MutableCollection = mapTo(ArrayList(), ::deepCopyMapper) 24 | 25 | fun List.deepCopy(): MutableList = mapTo(ArrayList(), ::deepCopyMapper) 26 | 27 | fun Set.deepCopy(): Set = map(::deepCopyMapper).toSet() -------------------------------------------------------------------------------- /runtime/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/DeepCopyable.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy 2 | 3 | interface DeepCopyable { 4 | fun deepCopy(): T 5 | } -------------------------------------------------------------------------------- /runtime/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/runtime/DeepCopyableCollection.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.runtime 2 | 3 | /** 4 | * Created by benny. 5 | */ 6 | fun Collection.deepCopy(): Collection = toList() 7 | 8 | fun List.deepCopy() = toList() 9 | 10 | fun Set.deepCopy() = toSet() 11 | 12 | fun Map.deepCopy() = toMap() 13 | 14 | @JvmName("mutableCopy") 15 | fun MutableCollection.deepCopy() = toMutableList() 16 | 17 | @JvmName("mutableCopy") 18 | fun MutableList.deepCopy() = toMutableList() 19 | 20 | @JvmName("mutableCopy") 21 | fun MutableSet.deepCopy() = toMutableSet() 22 | 23 | @JvmName("mutableCopy") 24 | fun MutableMap.deepCopy() = toMutableMap() 25 | 26 | inline fun Collection.deepCopy(deepCopyOfElement: (T) -> T) = map(deepCopyOfElement) 27 | 28 | inline fun List.deepCopy(deepCopyOfElement: (T) -> T) = map(deepCopyOfElement) 29 | 30 | inline fun Set.deepCopy(deepCopyOfElement: (T) -> T) = map(deepCopyOfElement) 31 | 32 | inline fun Map.deepCopy(deepCopyOfKey: (K) -> (K), deepCopyOfValue: (V) -> V) = map { 33 | deepCopyOfKey(it.key) to deepCopyOfValue(it.value) 34 | }.toMap() 35 | 36 | @JvmName("mutableCopy") 37 | inline fun MutableCollection.deepCopy(deepCopyOfElement: (T) -> T): MutableCollection = 38 | mapTo(ArrayList(), deepCopyOfElement) 39 | 40 | @JvmName("mutableCopy") 41 | inline fun MutableList.deepCopy(deepCopyOfElement: (T) -> T): MutableList = 42 | mapTo(ArrayList(), deepCopyOfElement) 43 | 44 | @JvmName("mutableCopy") 45 | inline fun MutableSet.deepCopy(deepCopyOfElement: (T) -> T) = 46 | mapTo(HashSet(), deepCopyOfElement) 47 | 48 | @JvmName("mutableCopy") 49 | inline fun MutableMap.deepCopy( 50 | deepCopyOfKey: (K) -> (K), 51 | deepCopyOfValue: (V) -> V 52 | ): MutableMap = map { 53 | deepCopyOfKey(it.key) to deepCopyOfValue(it.value) 54 | }.toMap(HashMap()) 55 | -------------------------------------------------------------------------------- /runtime/src/test/kotlin/com/bennyhuo/kotlin/deepcopy/runtime/BuiltinTypesTest.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.runtime 2 | 3 | import org.junit.Test 4 | 5 | class BuiltinTypesTest{ 6 | @Test 7 | fun test() { 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /runtime/src/test/kotlin/com/bennyhuo/kotlin/deepcopy/runtime/StarProjection.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.runtime 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * Created by Benny Huo on 2023/1/16 7 | */ 8 | class StarProjectionTest { 9 | @Test 10 | fun test() { 11 | val map: Map<*, *> = mutableMapOf("a" to 1, "b" to 2) 12 | println(map.deepCopy({ it }, { it })) 13 | val list: MutableList<*> = mutableListOf() 14 | println(list.deepCopy { it }) 15 | } 16 | } -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | ext.useksp = useksp == "true" -------------------------------------------------------------------------------- /sample/gradle.properties: -------------------------------------------------------------------------------- 1 | useksp=false 2 | shouldPublish=false -------------------------------------------------------------------------------- /sample/library/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.google.devtools.ksp") version "$ksp_version" 3 | id 'java' 4 | id 'org.jetbrains.kotlin.jvm' 5 | id "org.jetbrains.kotlin.kapt" 6 | } 7 | 8 | sourceCompatibility = 1.8 9 | 10 | dependencies { 11 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 12 | implementation(project(":annotations")) 13 | implementation(project(":runtime")) 14 | 15 | if (useksp) { 16 | ksp(project(":compiler:compiler-ksp")) 17 | kspTest project(":compiler:compiler-ksp") 18 | } else { 19 | kapt(project(":compiler:compiler-apt")) 20 | kaptTest project(":compiler:compiler-apt") 21 | } 22 | } 23 | 24 | if (useksp) sourceSets.main.java.srcDirs += "build/generated/ksp/main/kotlin" 25 | 26 | compileKotlin { 27 | kotlinOptions.jvmTarget = "1.8" 28 | } 29 | compileTestKotlin { 30 | kotlinOptions.jvmTarget = "1.8" 31 | } -------------------------------------------------------------------------------- /sample/library/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/library/PrebuiltClass.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.library 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 5 | 6 | @DeepCopy 7 | data class Hello( 8 | val first: P1, 9 | val second: P2 10 | ) 11 | 12 | 13 | @DeepCopyConfig(values = [Pair::class]) 14 | class Config -------------------------------------------------------------------------------- /sample/library/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/library/innerclass/InnerClasses.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample.innerclass 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | 5 | @DeepCopy 6 | data class Talk(val name: String, val speaker: Speaker) { 7 | @DeepCopy 8 | data class District(val name: String) 9 | 10 | @DeepCopy 11 | data class Location(val lat: Double, val lng: Double) 12 | 13 | @DeepCopy 14 | data class Company(val name: String, val location: Location, val district: District) 15 | 16 | @DeepCopy 17 | data class Speaker(val name: String, val age: Int, val company: Company) 18 | } -------------------------------------------------------------------------------- /sample/library/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/library/nullable/Nullables.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample.nullable 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | 5 | @DeepCopy 6 | data class User(val name: String) 7 | 8 | @DeepCopy 9 | data class Nullables(val user: User?, val list: List?) -------------------------------------------------------------------------------- /sample/sample-js/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.google.devtools.ksp") version kspVersion 4 | } 5 | 6 | version = "unspecified" 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | kotlin { 13 | js(IR) { 14 | nodejs { 15 | } 16 | binaries.executable() 17 | } 18 | 19 | sourceSets { 20 | val jsMain by getting { 21 | kotlin.srcDir("build/generated/ksp/js/jsMain/kotlin") 22 | 23 | dependencies { 24 | implementation(project(":annotations")) 25 | } 26 | } 27 | } 28 | } 29 | 30 | dependencies { 31 | "kspJs"(project(":compiler:compiler-ksp")) 32 | } 33 | -------------------------------------------------------------------------------- /sample/sample-js/src/jsMain/kotlin/Sample.kt: -------------------------------------------------------------------------------- 1 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 2 | 3 | @DeepCopy 4 | data class District(var name: String) 5 | 6 | @DeepCopy 7 | data class Location(var lat: Double, var lng: Double) 8 | 9 | @DeepCopy 10 | data class Company( 11 | var name: String, 12 | var location: Location, 13 | var district: District 14 | ) 15 | 16 | @DeepCopy 17 | data class Speaker(var name: String, var age: Int, var company: Company) 18 | 19 | @DeepCopy 20 | data class Talk(var name: String, var speaker: Speaker) 21 | 22 | fun main(args: Array) { 23 | val talk = Talk( 24 | "如何优雅地使用数据类", 25 | Speaker( 26 | "bennyhuo 不是算命的", 27 | 1, 28 | Company( 29 | "猿辅导", 30 | Location(39.9, 116.3), 31 | District("北京郊区") 32 | ) 33 | ) 34 | ) 35 | 36 | val copiedTalk = talk.deepCopy() 37 | copiedTalk.name = "Kotlin 编译器插件:我们不期待" 38 | copiedTalk.speaker.company = Company( 39 | "猿辅导", 40 | Location(39.9, 116.3), 41 | District("华鼎世家对面") 42 | ) 43 | println(talk) 44 | println(copiedTalk) 45 | println(talk === copiedTalk) 46 | println(talk.speaker === copiedTalk.speaker) 47 | } -------------------------------------------------------------------------------- /sample/sample-jvm/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.google.devtools.ksp") version "$ksp_version" 3 | id "java" 4 | id "org.jetbrains.kotlin.jvm" 5 | id "org.jetbrains.kotlin.kapt" 6 | } 7 | 8 | sourceCompatibility = 1.8 9 | 10 | dependencies { 11 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 12 | implementation(project(":annotations")) 13 | implementation(project(":runtime")) 14 | 15 | implementation(project(":sample:library")) 16 | 17 | if (useksp) { 18 | ksp(project(":compiler:compiler-ksp")) 19 | kspTest project(":compiler:compiler-ksp") 20 | } else { 21 | kapt(project(":compiler:compiler-apt")) 22 | kaptTest project(":compiler:compiler-apt") 23 | } 24 | } 25 | 26 | if (useksp) sourceSets.main.java.srcDirs += "build/generated/ksp/main/kotlin" 27 | 28 | compileKotlin { 29 | kotlinOptions.jvmTarget = "1.8" 30 | } 31 | compileTestKotlin { 32 | kotlinOptions.jvmTarget = "1.8" 33 | } -------------------------------------------------------------------------------- /sample/sample-jvm/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/sample/Sample.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | 5 | @DeepCopy 6 | data class District(var name: String) 7 | 8 | @DeepCopy 9 | data class Location(var lat: Double, var lng: Double) 10 | 11 | @DeepCopy 12 | data class Company( 13 | var name: String, 14 | var location: Location, 15 | var district: District 16 | ) 17 | 18 | @DeepCopy 19 | data class Speaker(var name: String, var age: Int, var company: Company) 20 | 21 | @DeepCopy 22 | data class Talk(var name: String, var speaker: Speaker) 23 | 24 | fun main(args: Array) { 25 | val talk = Talk( 26 | "如何优雅地使用数据类", 27 | Speaker( 28 | "bennyhuo 不是算命的", 29 | 1, 30 | Company( 31 | "猿辅导", 32 | Location(39.9, 116.3), 33 | District("北京郊区") 34 | ) 35 | ) 36 | ) 37 | 38 | val copiedTalk = talk.deepCopy() 39 | copiedTalk.name = "Kotlin 编译器插件:我们不期待" 40 | copiedTalk.speaker.company = Company( 41 | "猿辅导", 42 | Location(39.9, 116.3), 43 | District("华鼎世家对面") 44 | ) 45 | println(talk) 46 | println(copiedTalk) 47 | println(talk === copiedTalk) 48 | println(talk.speaker === copiedTalk.speaker) 49 | } -------------------------------------------------------------------------------- /sample/sample-jvm/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/sample/collection/Collections.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample.collection 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | 5 | @DeepCopy 6 | data class Team(val name: String, val workers: List) 7 | 8 | @DeepCopy 9 | data class Worker(val name: String) 10 | 11 | @DeepCopy 12 | data class Team2(val name: String, val workers: List, val pair: Pair, val map: Map){ 13 | @DeepCopy 14 | data class Worker2(val name: String) 15 | } 16 | 17 | -------------------------------------------------------------------------------- /sample/sample-jvm/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/sample/prebuilt/B.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample.prebuilt 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | import com.bennyhuo.kotlin.deepcopy.sample.nullable.User 5 | 6 | /** 7 | * Created by benny. 8 | */ 9 | @DeepCopy 10 | data class B( 11 | val pair: Pair, 12 | val triple: Triple, 13 | val user: User 14 | ) -------------------------------------------------------------------------------- /sample/sample-jvm/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/sample/prebuilt/PrebuiltClass.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample.prebuilt 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 5 | 6 | @DeepCopy 7 | data class Hello( 8 | val first: P1, 9 | val second: P2 10 | ) 11 | 12 | 13 | @DeepCopyConfig(values = [Triple::class]) 14 | class Config 15 | 16 | @DeepCopy 17 | data class A( 18 | val pair: Pair, 19 | var triple: Triple, 20 | val hello: Hello 21 | ) -------------------------------------------------------------------------------- /sample/sample-jvm/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/sample/recursive/IssueCopyLoop.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample.recursive 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | 5 | @DeepCopy 6 | data class Hello(var name: String) 7 | 8 | @DeepCopy 9 | data class DataStartParam constructor( 10 | var text: String, 11 | var hello1: Hello? = null, 12 | var hello2: Hello? = null, 13 | var hello3: Hello? = null 14 | ) -------------------------------------------------------------------------------- /sample/sample-jvm/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/sample/recursive/Recursive.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample.recursive 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | 5 | /** 6 | * Uncomment owner will lead to exception. 7 | * CopyLoopException: Detect infinite copy loop. 8 | * It will cause stack overflow to call com.bennyhuo.kotlin.deepcopy.sample.recursive.Owner.deepCopy() in the runtime. 9 | * */ 10 | //@DeepCopy 11 | //data class Project(val name: String, var owner: Owner?) 12 | 13 | /** 14 | * This is the correct way. Set up owner later after project initialized. 15 | */ 16 | @DeepCopy 17 | data class Project(val name: String){ 18 | lateinit var owner: Owner 19 | } 20 | 21 | @DeepCopy 22 | data class Owner(val project: Project) -------------------------------------------------------------------------------- /sample/sample-jvm/src/main/kotlin/com/bennyhuo/kotlin/deepcopy/sample/typealias/TypeAliases.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.kotlin.deepcopy.sample.`typealias` 2 | 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 4 | 5 | typealias X = HashMap 6 | 7 | @DeepCopy 8 | data class GenericParameter(val map: X>) 9 | 10 | @DeepCopy 11 | data class GenericParameterT( 12 | val map: X) 13 | 14 | fun main() { 15 | val value = GenericParameterT(hashMapOf(2 to "Hello")) 16 | val copied = value.deepCopy() 17 | println(value.map === copied.map) 18 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | dependencyResolutionManagement { 9 | repositories { 10 | mavenCentral() 11 | } 12 | } 13 | 14 | rootProject.name = 'deepcopy' 15 | 16 | include ":reflect-impl" 17 | include ":reflect-impl-js" 18 | 19 | include ":annotations" 20 | include ":runtime" 21 | include ":test-common" 22 | 23 | include ":compiler:compiler-ksp" 24 | include ":compiler:compiler-apt" 25 | 26 | include ":sample:library" 27 | include ":sample:sample-jvm" 28 | include ":sample:sample-js" 29 | 30 | include ":kcp-impl:compiler-kcp" 31 | include ":kcp-impl:compiler-kcp-embeddable" 32 | include ":kcp-impl:plugin-gradle" 33 | include ":kcp-impl:plugin-ide" 34 | 35 | def local = file("composite_build.local") 36 | if (local.exists()) { 37 | local.readLines().each { 38 | if (it != "") { 39 | def f = file("../$it") 40 | if (f.exists()) { 41 | includeBuild(f) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test-common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | repositories { 2 | mavenCentral() 3 | } 4 | plugins { 5 | kotlin("jvm") 6 | } 7 | 8 | dependencies { 9 | implementation(kotlin("stdlib")) 10 | implementation(project(":annotations")) 11 | 12 | testImplementation("com.bennyhuo.kotlin:kotlin-compile-testing-extensions:$compileTestingExtensionsVersion") 13 | 14 | testImplementation(project(":compiler:compiler-ksp")) 15 | testImplementation(project(":compiler:compiler-apt")) 16 | testImplementation(project(":runtime")) 17 | 18 | testImplementation(kotlin("test-common")) 19 | testImplementation(kotlin("test-annotations-common")) 20 | testImplementation(kotlin("test-junit")) 21 | } 22 | -------------------------------------------------------------------------------- /test-common/gradle.properties: -------------------------------------------------------------------------------- 1 | shouldPublish=false -------------------------------------------------------------------------------- /test-common/src/test/kotlin/com/benyhuo/kotlin/deepcopy/compiler/BaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.benyhuo.kotlin.deepcopy.compiler 2 | 3 | import com.bennyhuo.kotlin.compiletesting.extensions.module.KotlinModule 4 | import com.bennyhuo.kotlin.compiletesting.extensions.module.checkResult 5 | import com.bennyhuo.kotlin.compiletesting.extensions.source.FileBasedModuleInfoLoader 6 | import com.bennyhuo.kotlin.compiletesting.extensions.source.SourceModuleInfo 7 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 8 | import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly 9 | import java.io.File 10 | 11 | /** 12 | * Created by benny. 13 | */ 14 | @OptIn(ExperimentalCompilerApi::class) 15 | abstract class BaseTest { 16 | 17 | private val testCaseDirCommon = File("testData") 18 | 19 | abstract val testCaseDir: File 20 | 21 | private val extensions = arrayOf("", ".kt", ".txt") 22 | 23 | fun doTest(name: String? = null) { 24 | val caseName = name ?: retrieveTestCaseName() 25 | val testCaseFile = listOf(testCaseDir, testCaseDirCommon).firstNotNullOf { findTestCaseFile(it, caseName) } 26 | 27 | val loader = FileBasedModuleInfoLoader(testCaseFile.path) 28 | loader.loadSourceModuleInfos().map(::createKotlinModule) 29 | .checkResult( 30 | loader.loadExpectModuleInfos(), 31 | checkExitCode = false, 32 | checkGeneratedFiles = true, 33 | checkCompilerOutput = true 34 | ) 35 | } 36 | 37 | private fun retrieveTestCaseName(): String { 38 | return Throwable().stackTrace.first { 39 | it.className != BaseTest::class.java.name 40 | }.methodName 41 | } 42 | 43 | private fun findTestCaseFile(parentDir: File, name: String): File? { 44 | return extensions.firstNotNullOfOrNull { extension -> 45 | File(parentDir, "$name$extension").takeIf { 46 | it.exists() 47 | } ?: File( 48 | parentDir, "${name.capitalizeAsciiOnly()}$extension" 49 | ).takeIf { 50 | it.exists() 51 | } 52 | } 53 | } 54 | 55 | abstract fun createKotlinModule(moduleInfo: SourceModuleInfo): KotlinModule 56 | 57 | } 58 | -------------------------------------------------------------------------------- /test-common/src/test/kotlin/com/benyhuo/kotlin/deepcopy/compiler/KaptTest.kt: -------------------------------------------------------------------------------- 1 | package com.benyhuo.kotlin.deepcopy.compiler 2 | 3 | import com.bennyhuo.kotlin.compiletesting.extensions.module.KotlinModule 4 | import com.bennyhuo.kotlin.compiletesting.extensions.source.SourceModuleInfo 5 | import com.bennyhuo.kotlin.deepcopy.compiler.apt.DeepCopyProcessor 6 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 7 | import org.junit.Test 8 | import java.io.File 9 | 10 | /** 11 | * Created by benny at 2021/6/21 7:00. 12 | */ 13 | @OptIn(ExperimentalCompilerApi::class) 14 | class KaptTest : BaseTest() { 15 | @Test 16 | fun basic() = doTest() 17 | 18 | @Test 19 | fun collections() = doTest() 20 | 21 | @Test 22 | fun config() = doTest() 23 | 24 | @Test 25 | fun generics() = doTest() 26 | 27 | @Test 28 | fun genericsWithDeepCopyableBounds() = doTest() 29 | 30 | @Test 31 | fun innerClasses() = doTest() 32 | 33 | @Test 34 | fun modules() = doTest() 35 | 36 | @Test 37 | fun nullables() = doTest() 38 | 39 | @Test 40 | fun recursive() = doTest() 41 | 42 | @Test 43 | fun typeAliases() = doTest() 44 | 45 | override val testCaseDir: File = File("testData/kapt") 46 | 47 | override fun createKotlinModule(moduleInfo: SourceModuleInfo): KotlinModule { 48 | return KotlinModule(moduleInfo, annotationProcessors = listOf(DeepCopyProcessor())) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test-common/src/test/kotlin/com/benyhuo/kotlin/deepcopy/compiler/KspTest.kt: -------------------------------------------------------------------------------- 1 | package com.benyhuo.kotlin.deepcopy.compiler 2 | 3 | import com.bennyhuo.kotlin.compiletesting.extensions.module.KotlinModule 4 | import com.bennyhuo.kotlin.compiletesting.extensions.source.SourceModuleInfo 5 | import com.bennyhuo.kotlin.deepcopy.compiler.ksp.DeepCopySymbolProcessorProvider 6 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi 7 | import org.junit.Test 8 | import java.io.File 9 | 10 | /** 11 | * Created by benny at 2021/6/21 7:00. 12 | */ 13 | @OptIn(ExperimentalCompilerApi::class) 14 | class KspTest : BaseTest() { 15 | @Test 16 | fun basic() = doTest() 17 | 18 | @Test 19 | fun collections() = doTest() 20 | 21 | @Test 22 | fun config() = doTest() 23 | 24 | @Test 25 | fun generics() = doTest() 26 | 27 | @Test 28 | fun genericsWithDeepCopyableBounds() = doTest() 29 | 30 | @Test 31 | fun innerClasses() = doTest() 32 | 33 | @Test 34 | fun modules() = doTest() 35 | 36 | @Test 37 | fun nullables() = doTest() 38 | 39 | @Test 40 | fun recursive() = doTest() 41 | 42 | @Test 43 | fun typeAliases() = doTest() 44 | 45 | override val testCaseDir: File = File("testData/ksp") 46 | 47 | override fun createKotlinModule(moduleInfo: SourceModuleInfo): KotlinModule { 48 | return KotlinModule(moduleInfo, symbolProcessorProviders = listOf(DeepCopySymbolProcessorProvider())) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test-common/src/test/kotlin/com/benyhuo/kotlin/deepcopy/compiler/TestBase.kt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bennyhuo/KotlinDeepCopy/f8f8d54920108f1faca6522bac6e1a1ef9243c02/test-common/src/test/kotlin/com/benyhuo/kotlin/deepcopy/compiler/TestBase.kt -------------------------------------------------------------------------------- /test-common/testData/Basic.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | package com.bennyhuo.kotlin.deepcopy.sample 3 | 4 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 5 | 6 | @DeepCopy 7 | data class District(val name: String) 8 | 9 | @DeepCopy 10 | data class Location(val lat: Double, val lng: Double) 11 | 12 | @DeepCopy 13 | data class Company(val name: String, val location: Location, val district: District) 14 | 15 | @DeepCopy 16 | data class Speaker(val name: String, val age: Int, val company: Company) 17 | 18 | @DeepCopy 19 | data class Talk(val name: String, val speaker: Speaker) 20 | 21 | // EXPECT 22 | // FILE: Company$$DeepCopy.kt 23 | package com.bennyhuo.kotlin.deepcopy.sample 24 | 25 | import com.bennyhuo.kotlin.deepcopy.sample.deepCopy 26 | import kotlin.String 27 | import kotlin.jvm.JvmOverloads 28 | 29 | @JvmOverloads 30 | public fun Company.deepCopy( 31 | name: String = this.name, 32 | location: Location = this.location, 33 | district: District = this.district, 34 | ): Company = Company(name, location.deepCopy(), district.deepCopy()) 35 | // FILE: District$$DeepCopy.kt 36 | package com.bennyhuo.kotlin.deepcopy.sample 37 | 38 | import kotlin.String 39 | import kotlin.jvm.JvmOverloads 40 | 41 | @JvmOverloads 42 | public fun District.deepCopy(name: String = this.name): District = District(name) 43 | // FILE: Location$$DeepCopy.kt 44 | package com.bennyhuo.kotlin.deepcopy.sample 45 | 46 | import kotlin.Double 47 | import kotlin.jvm.JvmOverloads 48 | 49 | @JvmOverloads 50 | public fun Location.deepCopy(lat: Double = this.lat, lng: Double = this.lng): Location = 51 | Location(lat, lng) 52 | // FILE: Speaker$$DeepCopy.kt 53 | package com.bennyhuo.kotlin.deepcopy.sample 54 | 55 | import com.bennyhuo.kotlin.deepcopy.sample.deepCopy 56 | import kotlin.Int 57 | import kotlin.String 58 | import kotlin.jvm.JvmOverloads 59 | 60 | @JvmOverloads 61 | public fun Speaker.deepCopy( 62 | name: String = this.name, 63 | age: Int = this.age, 64 | company: Company = this.company, 65 | ): Speaker = Speaker(name, age, company.deepCopy()) 66 | // FILE: Talk$$DeepCopy.kt 67 | package com.bennyhuo.kotlin.deepcopy.sample 68 | 69 | import com.bennyhuo.kotlin.deepcopy.sample.deepCopy 70 | import kotlin.String 71 | import kotlin.jvm.JvmOverloads 72 | 73 | @JvmOverloads 74 | public fun Talk.deepCopy(name: String = this.name, speaker: Speaker = this.speaker): Talk = 75 | Talk(name, speaker.deepCopy()) 76 | 77 | -------------------------------------------------------------------------------- /test-common/testData/Collections.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 3 | 4 | @DeepCopy 5 | data class Team(val name: String, val workers: List) 6 | 7 | @DeepCopy 8 | data class Worker(val name: String) 9 | 10 | @DeepCopy 11 | data class Team2(val name: String, val workers: List, val pair: Pair, val map: Map){ 12 | @DeepCopy 13 | data class Worker2(val name: String) 14 | } 15 | 16 | // EXPECT 17 | // FILE: Team$$DeepCopy.kt 18 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 19 | import deepCopy 20 | import kotlin.String 21 | import kotlin.collections.List 22 | import kotlin.jvm.JvmOverloads 23 | 24 | @JvmOverloads 25 | public fun Team.deepCopy(name: String = this.name, workers: List = this.workers): Team = 26 | Team(name, workers.deepCopy { it.deepCopy() }) 27 | // FILE: Worker$$DeepCopy.kt 28 | import kotlin.String 29 | import kotlin.jvm.JvmOverloads 30 | 31 | @JvmOverloads 32 | public fun Worker.deepCopy(name: String = this.name): Worker = Worker(name) 33 | // FILE: Team2$$DeepCopy.kt 34 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 35 | import deepCopy 36 | import kotlin.Pair 37 | import kotlin.String 38 | import kotlin.collections.List 39 | import kotlin.collections.Map 40 | import kotlin.jvm.JvmOverloads 41 | 42 | @JvmOverloads 43 | public fun Team2.deepCopy( 44 | name: String = this.name, 45 | workers: List = this.workers, 46 | pair: Pair = this.pair, 47 | map: Map = this.map, 48 | ): Team2 = Team2(name, workers.deepCopy { it.deepCopy() }, pair, map.deepCopy({ it }, { 49 | it.deepCopy() })) 50 | // FILE: Worker2$$DeepCopy.kt 51 | import kotlin.String 52 | import kotlin.jvm.JvmOverloads 53 | 54 | @JvmOverloads 55 | public fun Team2.Worker2.deepCopy(name: String = this.name): Team2.Worker2 = Team2.Worker2(name) 56 | 57 | -------------------------------------------------------------------------------- /test-common/testData/GenericsWithDeepCopyableBounds.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 3 | import com.bennyhuo.kotlin.deepcopy.DeepCopyable 4 | 5 | @DeepCopy 6 | data class GenericParameter>(val a: A, val b: B, val c: List, val d: List) 7 | 8 | // EXPECT 9 | // FILE: GenericParameter$$DeepCopy.kt 10 | import com.bennyhuo.kotlin.deepcopy.DeepCopyable 11 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 12 | import kotlin.Number 13 | import kotlin.collections.List 14 | import kotlin.jvm.JvmOverloads 15 | 16 | @JvmOverloads 17 | public fun > GenericParameter.deepCopy( 18 | a: A = this.a, 19 | b: B = this.b, 20 | c: List = this.c, 21 | d: List = this.d, 22 | ): GenericParameter = GenericParameter(a, b.deepCopy(), c.deepCopy(), d.deepCopy { 23 | it.deepCopy() }) 24 | 25 | -------------------------------------------------------------------------------- /test-common/testData/InnerClasses.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 3 | 4 | @DeepCopy 5 | data class Talk(val name: String, val speaker: Speaker) { 6 | @DeepCopy 7 | data class District(val name: String) 8 | 9 | @DeepCopy 10 | data class Location(val lat: Double, val lng: Double) 11 | 12 | @DeepCopy 13 | data class Company(val name: String, val location: Location, val district: District) 14 | 15 | @DeepCopy 16 | data class Speaker(val name: String, val age: Int, val company: Company) 17 | } 18 | 19 | // EXPECT 20 | // FILE: Company$$DeepCopy.kt 21 | import deepCopy 22 | import kotlin.String 23 | import kotlin.jvm.JvmOverloads 24 | 25 | @JvmOverloads 26 | public fun Talk.Company.deepCopy( 27 | name: String = this.name, 28 | location: Talk.Location = this.location, 29 | district: Talk.District = this.district, 30 | ): Talk.Company = Talk.Company(name, location.deepCopy(), district.deepCopy()) 31 | // FILE: District$$DeepCopy.kt 32 | import kotlin.String 33 | import kotlin.jvm.JvmOverloads 34 | 35 | @JvmOverloads 36 | public fun Talk.District.deepCopy(name: String = this.name): Talk.District = Talk.District(name) 37 | // FILE: Location$$DeepCopy.kt 38 | import kotlin.Double 39 | import kotlin.jvm.JvmOverloads 40 | 41 | @JvmOverloads 42 | public fun Talk.Location.deepCopy(lat: Double = this.lat, lng: Double = this.lng): Talk.Location = 43 | Talk.Location(lat, lng) 44 | // FILE: Speaker$$DeepCopy.kt 45 | import deepCopy 46 | import kotlin.Int 47 | import kotlin.String 48 | import kotlin.jvm.JvmOverloads 49 | 50 | @JvmOverloads 51 | public fun Talk.Speaker.deepCopy( 52 | name: String = this.name, 53 | age: Int = this.age, 54 | company: Talk.Company = this.company, 55 | ): Talk.Speaker = Talk.Speaker(name, age, company.deepCopy()) 56 | // FILE: Talk$$DeepCopy.kt 57 | import deepCopy 58 | import kotlin.String 59 | import kotlin.jvm.JvmOverloads 60 | 61 | @JvmOverloads 62 | public fun Talk.deepCopy(name: String = this.name, speaker: Talk.Speaker = this.speaker): Talk = 63 | Talk(name, speaker.deepCopy()) 64 | -------------------------------------------------------------------------------- /test-common/testData/Modules.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | // MODULE: lib-a 3 | // FILE: Point.kt 4 | data class Point(var x: Int, var y: Int) 5 | // MODULE: lib-b / lib-a 6 | // FILE: Config.kt 7 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 8 | 9 | @DeepCopyConfig(values = [Point::class]) 10 | class Config 11 | // MODULE: main / lib-a, lib-b 12 | // FILE: Location.kt 13 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 14 | @DeepCopy 15 | data class Location(val name: String, val pointE06: Point) 16 | // EXPECT 17 | // MODULE: main 18 | // FILE: Location$$DeepCopy.kt 19 | import deepCopy 20 | import kotlin.String 21 | import kotlin.jvm.JvmOverloads 22 | 23 | @JvmOverloads 24 | public fun Location.deepCopy(name: String = this.name, pointE06: Point = this.pointE06): Location = 25 | Location(name, pointE06.deepCopy()) -------------------------------------------------------------------------------- /test-common/testData/Nullables.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 3 | 4 | @DeepCopy 5 | data class User(val name: String) 6 | 7 | @DeepCopy 8 | data class Nullables(val user: User?, val list: List?) 9 | 10 | // EXPECT 11 | // FILE: Nullables$$DeepCopy.kt 12 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 13 | import deepCopy 14 | import kotlin.collections.List 15 | import kotlin.jvm.JvmOverloads 16 | 17 | @JvmOverloads 18 | public fun Nullables.deepCopy(user: User? = this.user, list: List? = this.list): Nullables = 19 | Nullables(user?.deepCopy(), list?.deepCopy { it.deepCopy() }) 20 | // FILE: User$$DeepCopy.kt 21 | import kotlin.String 22 | import kotlin.jvm.JvmOverloads 23 | 24 | @JvmOverloads 25 | public fun User.deepCopy(name: String = this.name): User = User(name) 26 | -------------------------------------------------------------------------------- /test-common/testData/kapt/Config.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | // FILE: a.kt 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 4 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 5 | 6 | @DeepCopyConfig(values = [Pair::class, Triple::class]) 7 | class Config 8 | 9 | @DeepCopyConfig(values = [Pair::class]) 10 | class ConfigSingle 11 | 12 | // FILE: b.kt 13 | package test.b 14 | 15 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 16 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 17 | 18 | @DeepCopy 19 | data class B( 20 | val pair: Pair, 21 | var triple: Triple) 22 | 23 | // FILE: c.kt 24 | package test.c 25 | 26 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 27 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 28 | import test.b.B 29 | 30 | @DeepCopy 31 | data class C( 32 | val pair: Pair, 33 | var triple: Triple, 34 | val b: B) 35 | 36 | 37 | // EXPECT 38 | // FILE: Pair$$DeepCopy.kt 39 | package com.bennyhuo.kotlin.deepcopy.builtin 40 | 41 | import kotlin.Pair 42 | import kotlin.jvm.JvmOverloads 43 | 44 | @JvmOverloads 45 | public fun Pair.deepCopy(first: A = this.first, second: B = this.second): Pair = 46 | Pair(first, second) 47 | // FILE: Triple$$DeepCopy.kt 48 | package com.bennyhuo.kotlin.deepcopy.builtin 49 | 50 | import kotlin.Triple 51 | import kotlin.jvm.JvmOverloads 52 | 53 | @JvmOverloads 54 | public fun Triple.deepCopy( 55 | first: A = this.first, 56 | second: B = this.second, 57 | third: C = this.third, 58 | ): Triple = Triple(first, second, third) 59 | // FILE: DeepCopyIndex_f0b3dc34236b524cc3776f1753d70d87.java 60 | package com.bennyhuo.kotlin.processor.module; 61 | 62 | @LibraryIndex({"Config", "ConfigSingle"}) 63 | class DeepCopyIndex_f0b3dc34236b524cc3776f1753d70d87 { 64 | } 65 | // FILE: B$$DeepCopy.kt 66 | package test.b 67 | 68 | import com.bennyhuo.kotlin.deepcopy.builtin.deepCopy 69 | import kotlin.Any 70 | import kotlin.Int 71 | import kotlin.Pair 72 | import kotlin.String 73 | import kotlin.Triple 74 | import kotlin.jvm.JvmOverloads 75 | 76 | @JvmOverloads 77 | public fun B.deepCopy(pair: Pair = this.pair, triple: Triple = 78 | this.triple): B = B(pair.deepCopy(), triple.deepCopy()) 79 | // FILE: C$$DeepCopy.kt 80 | package test.c 81 | 82 | import com.bennyhuo.kotlin.deepcopy.builtin.deepCopy 83 | import kotlin.Any 84 | import kotlin.Int 85 | import kotlin.Pair 86 | import kotlin.String 87 | import kotlin.Triple 88 | import kotlin.jvm.JvmOverloads 89 | import test.b.B 90 | import test.b.deepCopy 91 | 92 | @JvmOverloads 93 | public fun C.deepCopy( 94 | pair: Pair = this.pair, 95 | triple: Triple = this.triple, 96 | b: B = this.b, 97 | ): C = C(pair.deepCopy(), triple.deepCopy(), b.deepCopy()) 98 | 99 | -------------------------------------------------------------------------------- /test-common/testData/kapt/Generics.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 3 | 4 | @DeepCopy 5 | data class GenericParameter(val map: HashMap>) 6 | 7 | @DeepCopy 8 | data class GenericParameterT(val map: HashMap) 9 | 10 | @DeepCopy 11 | data class GenericParameterOutT(val map: List) 12 | 13 | @DeepCopy 14 | data class StarProjection0(val list: List>) 15 | 16 | @DeepCopy 17 | data class StarProjection1(val list: List>) 18 | 19 | @DeepCopy 20 | data class StarProjection2(val map: Map<*, *>) 21 | 22 | @DeepCopy 23 | data class StarProjection3(val list: List<*>) 24 | 25 | @DeepCopy 26 | data class Variances(val map: HashMap) 27 | 28 | @DeepCopy 29 | data class Variances1(val map: HashMap>) 30 | 31 | // EXPECT 32 | // FILE: GenericParameter$$DeepCopy.kt 33 | import java.util.HashMap 34 | import kotlin.String 35 | import kotlin.collections.List 36 | import kotlin.jvm.JvmOverloads 37 | 38 | @JvmOverloads 39 | public fun GenericParameter.deepCopy(map: HashMap> = this.map): 40 | GenericParameter = GenericParameter(map) 41 | // FILE: GenericParameterOutT$$DeepCopy.kt 42 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 43 | import kotlin.Number 44 | import kotlin.collections.List 45 | import kotlin.jvm.JvmOverloads 46 | 47 | @JvmOverloads 48 | public fun GenericParameterOutT.deepCopy(map: List = this.map): 49 | GenericParameterOutT = GenericParameterOutT(map.deepCopy()) 50 | // FILE: GenericParameterT$$DeepCopy.kt 51 | import java.util.HashMap 52 | import kotlin.Number 53 | import kotlin.jvm.JvmOverloads 54 | 55 | @JvmOverloads 56 | public fun GenericParameterT.deepCopy(map: HashMap = this.map): 57 | GenericParameterT = GenericParameterT(map) 58 | // FILE: StarProjection0$$DeepCopy.kt 59 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 60 | import kotlin.String 61 | import kotlin.Triple 62 | import kotlin.collections.List 63 | import kotlin.jvm.JvmOverloads 64 | 65 | @JvmOverloads 66 | public fun StarProjection0.deepCopy(list: List> = this.list): StarProjection0 = 67 | StarProjection0(list.deepCopy()) 68 | // FILE: StarProjection1$$DeepCopy.kt 69 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 70 | import kotlin.String 71 | import kotlin.collections.List 72 | import kotlin.collections.Map 73 | import kotlin.jvm.JvmOverloads 74 | 75 | @JvmOverloads 76 | public fun StarProjection1.deepCopy(list: List> = this.list): StarProjection1 = 77 | StarProjection1(list.deepCopy()) 78 | // FILE: StarProjection2$$DeepCopy.kt 79 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 80 | import kotlin.collections.Map 81 | import kotlin.jvm.JvmOverloads 82 | 83 | @JvmOverloads 84 | public fun StarProjection2.deepCopy(map: Map<*, *> = this.map): StarProjection2 = 85 | StarProjection2(map.deepCopy({ it }, { it })) 86 | // FILE: StarProjection3$$DeepCopy.kt 87 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 88 | import kotlin.collections.List 89 | import kotlin.jvm.JvmOverloads 90 | 91 | @JvmOverloads 92 | public fun StarProjection3.deepCopy(list: List<*> = this.list): StarProjection3 = 93 | StarProjection3(list.deepCopy()) 94 | // FILE: Variances$$DeepCopy.kt 95 | import java.util.HashMap 96 | import kotlin.Number 97 | import kotlin.String 98 | import kotlin.jvm.JvmOverloads 99 | 100 | @JvmOverloads 101 | public fun Variances.deepCopy(map: HashMap = this.map): Variances = 102 | Variances(map) 103 | // FILE: Variances1$$DeepCopy.kt 104 | import java.util.HashMap 105 | import kotlin.Number 106 | import kotlin.String 107 | import kotlin.collections.List 108 | import kotlin.jvm.JvmOverloads 109 | 110 | @JvmOverloads 111 | public fun Variances1.deepCopy(map: HashMap> = this.map): Variances1 = 112 | Variances1(map) 113 | 114 | -------------------------------------------------------------------------------- /test-common/testData/kapt/Recursive.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 3 | 4 | /** 5 | * Uncomment owner will lead to exception. 6 | * CopyLoopException: Detect infinite copy loop. 7 | * It will cause stack overflow to call com.bennyhuo.kotlin.deepcopy.sample.recursive.Owner.deepCopy() in the runtime. 8 | * */ 9 | @DeepCopy 10 | data class Project(val name: String, var owner: Owner?) 11 | 12 | /** 13 | * This is the correct way. Set up owner later after project initialized. 14 | */ 15 | //@DeepCopy 16 | //data class Project(val name: String){ 17 | // lateinit var owner: Owner 18 | //} 19 | 20 | @DeepCopy 21 | data class Owner(val project: Project) 22 | // EXPECT 23 | // FILE: compiles.log 24 | COMPILATION_ERROR 25 | e: Project.java: (9, -1): error: Detect infinite copy loop. It will cause stack overflow to call Project.deepCopy() in the runtime. 26 | -------------------------------------------------------------------------------- /test-common/testData/kapt/TypeAliases.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 3 | 4 | typealias X = HashMap 5 | 6 | @DeepCopy 7 | data class GenericParameter(val map: X>) 8 | 9 | @DeepCopy 10 | data class GenericParameterT(val map: X) 11 | 12 | // EXPECT 13 | // FILE: GenericParameter$$DeepCopy.kt 14 | import java.util.HashMap 15 | import kotlin.String 16 | import kotlin.collections.List 17 | import kotlin.jvm.JvmOverloads 18 | 19 | @JvmOverloads 20 | public fun GenericParameter.deepCopy(map: HashMap> = this.map): 21 | GenericParameter = GenericParameter(map) 22 | // FILE: GenericParameterT$$DeepCopy.kt 23 | import java.util.HashMap 24 | import kotlin.Number 25 | import kotlin.jvm.JvmOverloads 26 | 27 | @JvmOverloads 28 | public fun GenericParameterT.deepCopy(map: HashMap = this.map): 29 | GenericParameterT = GenericParameterT(map) 30 | -------------------------------------------------------------------------------- /test-common/testData/ksp/Config.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | // FILE: a.kt 3 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 4 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 5 | 6 | @DeepCopyConfig(values = [Pair::class, Triple::class]) 7 | class Config 8 | 9 | @DeepCopyConfig(values = [Pair::class]) 10 | class ConfigSingle 11 | 12 | // FILE: b.kt 13 | package test.b 14 | 15 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 16 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 17 | 18 | @DeepCopy 19 | data class B( 20 | val pair: Pair, 21 | var triple: Triple) 22 | 23 | // FILE: c.kt 24 | package test.c 25 | 26 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopyConfig 27 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 28 | import test.b.B 29 | 30 | @DeepCopy 31 | data class C( 32 | val pair: Pair, 33 | var triple: Triple, 34 | val b: B) 35 | 36 | 37 | // EXPECT 38 | // FILE: Pair$$DeepCopy.kt 39 | package com.bennyhuo.kotlin.deepcopy.builtin 40 | 41 | import kotlin.Pair 42 | import kotlin.jvm.JvmOverloads 43 | 44 | @JvmOverloads 45 | public fun Pair.deepCopy(first: A = this.first, second: B = this.second): Pair = 46 | Pair(first, second) 47 | // FILE: Triple$$DeepCopy.kt 48 | package com.bennyhuo.kotlin.deepcopy.builtin 49 | 50 | import kotlin.Triple 51 | import kotlin.jvm.JvmOverloads 52 | 53 | @JvmOverloads 54 | public fun Triple.deepCopy( 55 | first: A = this.first, 56 | second: B = this.second, 57 | third: C = this.third, 58 | ): Triple = Triple(first, second, third) 59 | // FILE: DeepCopyIndex_1d3454b57a92b7b99ce53008332af4b8.kt 60 | package com.bennyhuo.kotlin.processor.module 61 | 62 | @LibraryIndex(value = ["ConfigSingle|0", "Config|0"]) 63 | public class DeepCopyIndex_1d3454b57a92b7b99ce53008332af4b8 64 | // FILE: B$$DeepCopy.kt 65 | package test.b 66 | 67 | import com.bennyhuo.kotlin.deepcopy.builtin.deepCopy 68 | import kotlin.Any 69 | import kotlin.Int 70 | import kotlin.Pair 71 | import kotlin.String 72 | import kotlin.Triple 73 | import kotlin.jvm.JvmOverloads 74 | 75 | @JvmOverloads 76 | public fun B.deepCopy(pair: Pair = this.pair, triple: Triple = 77 | this.triple): B = B(pair.deepCopy(), triple.deepCopy()) 78 | // FILE: C$$DeepCopy.kt 79 | package test.c 80 | 81 | import com.bennyhuo.kotlin.deepcopy.builtin.deepCopy 82 | import kotlin.Any 83 | import kotlin.Int 84 | import kotlin.Pair 85 | import kotlin.String 86 | import kotlin.Triple 87 | import kotlin.jvm.JvmOverloads 88 | import test.b.B 89 | import test.b.deepCopy 90 | 91 | @JvmOverloads 92 | public fun C.deepCopy( 93 | pair: Pair = this.pair, 94 | triple: Triple = this.triple, 95 | b: B = this.b, 96 | ): C = C(pair.deepCopy(), triple.deepCopy(), b.deepCopy()) 97 | 98 | -------------------------------------------------------------------------------- /test-common/testData/ksp/Generics.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 3 | 4 | @DeepCopy 5 | data class GenericParameter(val map: HashMap>) 6 | 7 | @DeepCopy 8 | data class GenericParameterT(val map: HashMap) 9 | 10 | @DeepCopy 11 | data class GenericParameterOutT(val map: List) 12 | 13 | @DeepCopy 14 | data class StarProjection0(val list: List>) 15 | 16 | @DeepCopy 17 | data class StarProjection1(val list: List>) 18 | 19 | @DeepCopy 20 | data class StarProjection2(val map: Map<*, *>) 21 | 22 | @DeepCopy 23 | data class StarProjection3(val list: List<*>) 24 | 25 | @DeepCopy 26 | data class Variances(val map: HashMap) 27 | 28 | @DeepCopy 29 | data class Variances1(val map: HashMap>) 30 | 31 | // EXPECT 32 | // FILE: GenericParameter$$DeepCopy.kt 33 | import kotlin.String 34 | import kotlin.collections.HashMap 35 | import kotlin.collections.List 36 | import kotlin.jvm.JvmOverloads 37 | 38 | @JvmOverloads 39 | public fun GenericParameter.deepCopy(map: HashMap> = this.map): 40 | GenericParameter = GenericParameter(map) 41 | // FILE: GenericParameterOutT$$DeepCopy.kt 42 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 43 | import kotlin.Number 44 | import kotlin.collections.List 45 | import kotlin.jvm.JvmOverloads 46 | 47 | @JvmOverloads 48 | public fun GenericParameterOutT.deepCopy(map: List = this.map): 49 | GenericParameterOutT = GenericParameterOutT(map.deepCopy()) 50 | // FILE: GenericParameterT$$DeepCopy.kt 51 | import kotlin.Number 52 | import kotlin.collections.HashMap 53 | import kotlin.jvm.JvmOverloads 54 | 55 | @JvmOverloads 56 | public fun GenericParameterT.deepCopy(map: HashMap = this.map): 57 | GenericParameterT = GenericParameterT(map) 58 | // FILE: StarProjection0$$DeepCopy.kt 59 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 60 | import kotlin.String 61 | import kotlin.Triple 62 | import kotlin.collections.List 63 | import kotlin.jvm.JvmOverloads 64 | 65 | @JvmOverloads 66 | public fun StarProjection0.deepCopy(list: List> = this.list): StarProjection0 = 67 | StarProjection0(list.deepCopy()) 68 | // FILE: StarProjection1$$DeepCopy.kt 69 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 70 | import kotlin.String 71 | import kotlin.collections.List 72 | import kotlin.collections.Map 73 | import kotlin.jvm.JvmOverloads 74 | 75 | @JvmOverloads 76 | public fun StarProjection1.deepCopy(list: List> = this.list): StarProjection1 = 77 | StarProjection1(list.deepCopy()) 78 | // FILE: StarProjection2$$DeepCopy.kt 79 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 80 | import kotlin.collections.Map 81 | import kotlin.jvm.JvmOverloads 82 | 83 | @JvmOverloads 84 | public fun StarProjection2.deepCopy(map: Map<*, *> = this.map): StarProjection2 = 85 | StarProjection2(map.deepCopy({ it }, { it })) 86 | // FILE: StarProjection3$$DeepCopy.kt 87 | import com.bennyhuo.kotlin.deepcopy.runtime.deepCopy 88 | import kotlin.collections.List 89 | import kotlin.jvm.JvmOverloads 90 | 91 | @JvmOverloads 92 | public fun StarProjection3.deepCopy(list: List<*> = this.list): StarProjection3 = 93 | StarProjection3(list.deepCopy()) 94 | // FILE: Variances$$DeepCopy.kt 95 | import kotlin.Number 96 | import kotlin.String 97 | import kotlin.collections.HashMap 98 | import kotlin.jvm.JvmOverloads 99 | 100 | @JvmOverloads 101 | public fun Variances.deepCopy(map: HashMap = this.map): Variances = 102 | Variances(map) 103 | // FILE: Variances1$$DeepCopy.kt 104 | import kotlin.Number 105 | import kotlin.String 106 | import kotlin.collections.HashMap 107 | import kotlin.collections.List 108 | import kotlin.jvm.JvmOverloads 109 | 110 | @JvmOverloads 111 | public fun Variances1.deepCopy(map: HashMap> = this.map): Variances1 = 112 | Variances1(map) 113 | 114 | -------------------------------------------------------------------------------- /test-common/testData/ksp/Recursive.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 3 | 4 | /** 5 | * Uncomment owner will lead to exception. 6 | * CopyLoopException: Detect infinite copy loop. 7 | * It will cause stack overflow to call com.bennyhuo.kotlin.deepcopy.sample.recursive.Owner.deepCopy() in the runtime. 8 | * */ 9 | @DeepCopy 10 | data class Project(val name: String, var owner: Owner?) 11 | 12 | /** 13 | * This is the correct way. Set up owner later after project initialized. 14 | */ 15 | //@DeepCopy 16 | //data class Project(val name: String){ 17 | // lateinit var owner: Owner 18 | //} 19 | 20 | @DeepCopy 21 | data class Owner(val project: Project) 22 | // EXPECT 23 | // FILE: compiles.log 24 | COMPILATION_ERROR 25 | e: DefaultFile.kt: (9, -1): Detect infinite copy loop. It will cause stack overflow to call Project.deepCopy() in the runtime. 26 | 27 | -------------------------------------------------------------------------------- /test-common/testData/ksp/TypeAliases.kt: -------------------------------------------------------------------------------- 1 | // SOURCE 2 | import com.bennyhuo.kotlin.deepcopy.annotations.DeepCopy 3 | 4 | typealias X = HashMap 5 | 6 | @DeepCopy 7 | data class GenericParameter(val map: X>) 8 | 9 | @DeepCopy 10 | data class GenericParameterT(val map: X) 11 | 12 | // EXPECT 13 | // FILE: GenericParameter$$DeepCopy.kt 14 | import kotlin.String 15 | import kotlin.collections.List 16 | import kotlin.jvm.JvmOverloads 17 | 18 | @JvmOverloads 19 | public fun GenericParameter.deepCopy(map: X> = this.map): GenericParameter = 20 | GenericParameter(map) 21 | // FILE: GenericParameterT$$DeepCopy.kt 22 | import kotlin.Number 23 | import kotlin.jvm.JvmOverloads 24 | 25 | @JvmOverloads 26 | public fun GenericParameterT.deepCopy(map: X = this.map): 27 | GenericParameterT = GenericParameterT(map) 28 | 29 | -------------------------------------------------------------------------------- /upload.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | clear 3 | ./gradlew :reflect-impl:clean :reflect-impl:assemble :reflect-impl:generatePomFileForMavenPublication :reflect-impl:bintrayUpload 4 | ./gradlew :annotations:clean :annotations:assemble :annotations:generatePomFileForMavenPublication :annotations:bintrayUpload 5 | ./gradlew :apt-impl:compiler:clean :apt-impl:compiler:assemble :apt-impl:compiler:generatePomFileForMavenPublication :apt-impl:compiler:bintrayUpload 6 | ./gradlew :runtime:clean :runtime:assemble :runtime:generatePomFileForMavenPublication :runtime:bintrayUpload --------------------------------------------------------------------------------