diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8cb88c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,106 @@ +###OSX### + +.DS_Store +.AppleDouble +.LSOverride + +# Icon must ends with two \r. +Icon + + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + + +###Linux### + +*~ + +# KDE directory preferences +.directory + + +###Android### + +# Built application files +*.apk +*.ap_ + +# Files for ART and Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ + +# Gradle files +.gradle/ +.gradletasknamecache +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Lint +lint-report.html +lint-report_files/ +lint_result.txt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +###IntelliJ### + +*.iml +*.ipr +*.iws +.idea/ + + +###Eclipse### + +*.pydevproject +.metadata +tmp/ +*.tmp +*.bak +*.swp +*~.nib +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse diff --git a/README.md b/README.md new file mode 100644 index 0000000..8bcf3f0 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +CopyDynamic +=========== + +PoC prototype of generating `copyDynamic` extension functions for data classes, such that you can do this: + +```kotlin +@CopyDynamic +data class Foo(val bar: String = "bar", val baz: String = "baz", val fizz: String = "fizz") +``` + +And have a `copyDynamic` extension function generated to allow for dynamic setting of variables in a copy call. + +```kotlin +val foo = Foo() + +val newFoo = foo.copyDynamic { + bar = "newBar" + if (someCondition) baz = "newBaz" +} +``` + + + +License +------- + + Copyright (C) 2018 Zac Sweers + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..acfcfb0 --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Zac Sweers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +allprojects { + buildscript { + repositories { + mavenCentral() + } + } + repositories { + mavenCentral() + } +} diff --git a/copydynamic-annotations/build.gradle b/copydynamic-annotations/build.gradle new file mode 100644 index 0000000..d47d13e --- /dev/null +++ b/copydynamic-annotations/build.gradle @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Zac Sweers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +buildscript { + ext.kotlin_version = '1.2.41' + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +group 'com.example' +version '1.0-SNAPSHOT' + +apply plugin: 'org.jetbrains.kotlin.jvm' + +repositories { + mavenCentral() +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} + +compileKotlin { + kotlinOptions { + freeCompilerArgs = ['-Xjsr305=strict'] + } +} diff --git a/copydynamic-annotations/src/main/kotlin/io/sweers/copydynamic/annotations/CopyDynamic.kt b/copydynamic-annotations/src/main/kotlin/io/sweers/copydynamic/annotations/CopyDynamic.kt new file mode 100644 index 0000000..5fa0e36 --- /dev/null +++ b/copydynamic-annotations/src/main/kotlin/io/sweers/copydynamic/annotations/CopyDynamic.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Zac Sweers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sweers.copydynamic.annotations + +import kotlin.annotation.AnnotationRetention.BINARY +import kotlin.annotation.AnnotationTarget.CLASS +import kotlin.annotation.AnnotationTarget.TYPE + +@Target(TYPE, CLASS) +@Retention(BINARY) +annotation class CopyDynamic diff --git a/copydynamic/build.gradle b/copydynamic/build.gradle new file mode 100644 index 0000000..ebdd580 --- /dev/null +++ b/copydynamic/build.gradle @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 Zac Sweers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +buildscript { + ext.kotlin_version = '1.2.41' + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'org.jetbrains.kotlin.jvm' +apply plugin: 'org.jetbrains.kotlin.kapt' + +repositories { + mavenCentral() + maven { + url "https://kotlin.bintray.com/kotlinx" + } +} + +dependencies { + kapt "com.google.auto.service:auto-service:1.0-rc4" + compileOnly "com.google.auto.service:auto-service:1.0-rc4" + + compile project(':copydynamic-annotations') + + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + compile 'com.squareup:kotlinpoet:0.7.0' + compile 'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.0.2' +} + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = ['-Xjsr305=strict'] + } +} diff --git a/copydynamic/src/main/kotlin/io/sweers/copydynamic/CopyDynamicProcessor.kt b/copydynamic/src/main/kotlin/io/sweers/copydynamic/CopyDynamicProcessor.kt new file mode 100644 index 0000000..04fee70 --- /dev/null +++ b/copydynamic/src/main/kotlin/io/sweers/copydynamic/CopyDynamicProcessor.kt @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2018 Zac Sweers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sweers.copydynamic + +import com.google.auto.common.MoreElements +import com.google.auto.service.AutoService +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier.PRIVATE +import com.squareup.kotlinpoet.LambdaTypeName +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.UNIT +import com.squareup.kotlinpoet.asClassName +import io.sweers.copydynamic.annotations.CopyDynamic +import kotlinx.metadata.impl.extensions.MetadataExtensions +import kotlinx.metadata.jvm.KotlinClassMetadata.Class +import java.io.File +import java.util.ServiceLoader +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.Filer +import javax.annotation.processing.Messager +import javax.annotation.processing.ProcessingEnvironment +import javax.annotation.processing.Processor +import javax.annotation.processing.RoundEnvironment +import javax.lang.model.SourceVersion +import javax.lang.model.element.TypeElement +import javax.lang.model.util.Elements +import javax.lang.model.util.Types + +@AutoService(Processor::class) +class CopyDynamicProcessor : AbstractProcessor() { + + private lateinit var filer: Filer + private lateinit var messager: Messager + private lateinit var elements: Elements + private lateinit var types: Types + private lateinit var options: Map + private lateinit var outputDir: File + + override fun init(processingEnv: ProcessingEnvironment) { + super.init(processingEnv) + filer = processingEnv.filer + messager = processingEnv.messager + elements = processingEnv.elementUtils + types = processingEnv.typeUtils + options = processingEnv.options + outputDir = options["kapt.kotlin.generated"]?.let(::File) ?: throw IllegalStateException( + "No kapt.kotlin.generated option provided") + } + + override fun getSupportedSourceVersion(): SourceVersion { + return SourceVersion.latestSupported() + } + + override fun getSupportedAnnotationTypes(): Set { + return setOf(CopyDynamic::class.java.canonicalName) + } + + override fun process(annotations: Set, + roundEnv: RoundEnvironment): Boolean { + + if (ServiceLoader.load(MetadataExtensions::class.java).toList().isEmpty()) { + throw RuntimeException("No metadata extensions found!") + } + + roundEnv.getElementsAnnotatedWith(CopyDynamic::class.java) + .asSequence() + .map { it as TypeElement } + .associate { + it to ((it.readMetadata()?.readKotlinClassMetadata() as? Class)?.readClassData() + ?: throw IllegalArgumentException("$it is not a kotlin class!")) + } + .forEach(this::createType) + + return true + } + + private fun createType(element: TypeElement, classData: ClassData) { + val packageName = MoreElements.getPackage(element).toString() + val builderName = "${element.simpleName}Builder" + val sourceType = element.asClassName() + val sourceParam = ParameterSpec.builder("source", sourceType).build() + val properties = mutableListOf>() + val builderSpec = TypeSpec.classBuilder(builderName) + .primaryConstructor(FunSpec.constructorBuilder() + .addModifiers(PRIVATE) + .addParameter(sourceParam) + .build()) + .addProperty(PropertySpec.builder(sourceParam.name, sourceType) + .addModifiers(PRIVATE) + .initializer("%N", sourceParam) + .build()) + .apply { + classData.properties.forEach { property -> + addProperty(PropertySpec.varBuilder(property.name, property.type) + .initializer("%N.%L", sourceParam, property.name) + .build() + .also { properties.add(property to it) } + ) + } + } + .addFunction(FunSpec.builder("build") + .addModifiers(PRIVATE) + .returns(sourceType) + .addStatement( + "return %N.copy(${properties.joinToString(", ") { "${it.first.name} = %N" }})", + sourceParam, + *(properties.map { it.second }.toTypedArray())) + .build()) + .build() + + // Generate the extension fun + val builderSpecKind = ClassName(packageName, builderName) + val copyBlockParam = ParameterSpec.builder("copyBlock", + LambdaTypeName.get(receiver = builderSpecKind, + parameters = emptyList(), + returnType = UNIT + )).build() + val extensionFun = FunSpec.builder("copyDynamic") + .receiver(sourceType) + .returns(sourceType) + .addParameter(copyBlockParam) + .addStatement("return %T(this).also { %N(it) }.build()", builderSpecKind, copyBlockParam) + .build() + + FileSpec.builder(packageName, builderName) + .addType(builderSpec) + .addFunction(extensionFun) + .build() + .writeTo(outputDir) + } +} diff --git a/copydynamic/src/main/kotlin/io/sweers/copydynamic/kotlintypes.kt b/copydynamic/src/main/kotlin/io/sweers/copydynamic/kotlintypes.kt new file mode 100644 index 0000000..e1dc25b --- /dev/null +++ b/copydynamic/src/main/kotlin/io/sweers/copydynamic/kotlintypes.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Zac Sweers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.sweers.copydynamic + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.TypeName + +internal fun TypeName.rawType(): ClassName { + return when (this) { + is ClassName -> this + is ParameterizedTypeName -> rawType + else -> throw IllegalArgumentException("Cannot get raw type from $this") + } +} + +internal fun TypeName.asNullableIf(condition: Boolean): TypeName { + return if (condition) asNullable() else this +} diff --git a/copydynamic/src/main/kotlin/io/sweers/copydynamic/metadata.kt b/copydynamic/src/main/kotlin/io/sweers/copydynamic/metadata.kt new file mode 100644 index 0000000..34501ca --- /dev/null +++ b/copydynamic/src/main/kotlin/io/sweers/copydynamic/metadata.kt @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2018 Zac Sweers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.sweers.copydynamic + +import com.squareup.kotlinpoet.ANY +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeVariableName +import com.squareup.kotlinpoet.WildcardTypeName +import kotlinx.metadata.Flag +import kotlinx.metadata.Flags +import kotlinx.metadata.KmClassVisitor +import kotlinx.metadata.KmConstructorVisitor +import kotlinx.metadata.KmPropertyVisitor +import kotlinx.metadata.KmTypeParameterVisitor +import kotlinx.metadata.KmTypeVisitor +import kotlinx.metadata.KmValueParameterVisitor +import kotlinx.metadata.KmVariance +import kotlinx.metadata.jvm.KotlinClassHeader +import kotlinx.metadata.jvm.KotlinClassMetadata +import javax.lang.model.element.Element +import javax.lang.model.element.ExecutableElement + +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +internal fun Element.readMetadata(): KotlinClassHeader? { + return getAnnotation(Metadata::class.java)?.run { + KotlinClassHeader(k, mv, bv, d1, d2, xs, pn, xi) + } +} + +internal fun KotlinClassHeader.readKotlinClassMetadata(): KotlinClassMetadata? { + return KotlinClassMetadata.read(this) +} + +internal fun KmVariance.asKModifier(): KModifier? { + return when (this) { + KmVariance.IN -> KModifier.IN + KmVariance.OUT -> KModifier.OUT + KmVariance.INVARIANT -> null + } +} + +/** + * Resolves the TypeName of this type as it would be seen in the source code, including nullability + * and generic type parameters. + * + * @param flags the [Flags] associated with this type + * @param [getTypeParameter] a function that returns the type parameter for the given index. **Only + * called if [TypeNameKmTypeVisitor.visitTypeParameter] is called** + * @param useTypeAlias indicates whether or not to use type aliases or resolve their underlying + * types + */ +internal class TypeNameKmTypeVisitor(flags: Flags, + private val getTypeParameter: ((index: Int) -> TypeName)? = null, + private val useTypeAlias: Boolean = true, + private val receiver: (TypeName) -> Unit) : KmTypeVisitor() { + + private val nullable = Flag.Type.IS_NULLABLE(flags) + private var className: String? = null + private var typeAliasName: String? = null + private var typeAliasType: TypeName? = null + private var flexibleTypeUpperBound: TypeName? = null + private var outerType: TypeName? = null + private var typeParameter: TypeName? = null + private val argumentList = mutableListOf() + + override fun visitAbbreviatedType(flags: Flags): KmTypeVisitor? { + if (!useTypeAlias) { + return null + } + return TypeNameKmTypeVisitor(flags, getTypeParameter, useTypeAlias) { + typeAliasType = it + } + } + + override fun visitArgument(flags: Flags, variance: KmVariance): KmTypeVisitor? { + return TypeNameKmTypeVisitor(flags, getTypeParameter, useTypeAlias) { + argumentList.add( + when (variance) { + KmVariance.IN -> WildcardTypeName.supertypeOf(it) + KmVariance.OUT -> WildcardTypeName.subtypeOf(it) + KmVariance.INVARIANT -> it + } + ) + } + } + + override fun visitClass(name: kotlinx.metadata.ClassName) { + className = name + } + + override fun visitFlexibleTypeUpperBound(flags: Flags, + typeFlexibilityId: String?): KmTypeVisitor? { + return TypeNameKmTypeVisitor(flags, getTypeParameter, useTypeAlias) { + flexibleTypeUpperBound = WildcardTypeName.subtypeOf(it) + } + } + + override fun visitOuterType(flags: Flags): KmTypeVisitor? { + return TypeNameKmTypeVisitor(flags, getTypeParameter, useTypeAlias) { + outerType = WildcardTypeName.supertypeOf(it) + } + } + + override fun visitStarProjection() { + argumentList.add(WildcardTypeName.subtypeOf(ANY)) + } + + override fun visitTypeAlias(name: kotlinx.metadata.ClassName) { + if (!useTypeAlias) { + return + } + typeAliasName = name + } + + override fun visitTypeParameter(id: Int) { + typeParameter = getTypeParameter?.invoke(id) ?: throw IllegalStateException( + "Visiting TypeParameter when there are no type parameters!") + } + + override fun visitEnd() { + var finalType = flexibleTypeUpperBound ?: outerType ?: typeParameter + if (finalType == null) { + if (useTypeAlias) { + finalType = typeAliasName?.let { ClassName.bestGuess(it.replace("/", ".")) } + } + if (finalType == null) { + finalType = typeAliasType ?: className?.let { ClassName.bestGuess(it.replace("/", ".")) } + ?: throw IllegalStateException("No valid typename found!") + } + } + + finalType = finalType.asNullableIf(nullable) + receiver(finalType) + } +} + +internal fun KotlinClassMetadata.Class.readClassData(): ClassData { + @Suppress("RedundantExplicitType") + var classFlags: Flags = 0 + lateinit var className: String + var constructorData: ConstructorData? = null + var companionObjectName: String? = null + val typeParameters = LinkedHashMap() + val typeParamResolver = { id: Int -> typeParameters[id]!! } + val superTypes = mutableListOf() + val properties = mutableListOf() + accept(object : KmClassVisitor() { + override fun visit(flags: Flags, name: kotlinx.metadata.ClassName) { + super.visit(flags, name) + className = name + classFlags = flags + } + + override fun visitTypeParameter(flags: Flags, + name: String, + id: Int, + variance: KmVariance): KmTypeParameterVisitor? { + return object : KmTypeParameterVisitor() { + val upperBoundList = mutableListOf() + override fun visitUpperBound(flags: Flags): KmTypeVisitor? { + return TypeNameKmTypeVisitor(flags) { + upperBoundList += it + } + } + + override fun visitEnd() { + typeParameters[id] = TypeVariableName( + name = name, + bounds = *(upperBoundList.toTypedArray()), + variance = variance.asKModifier().let { + if (it == KModifier.OUT) { + // We don't redeclare out variance here + null + } else { + it + } + } + ) + .reified(Flag.TypeParameter.IS_REIFIED(flags)) + } + } + } + + override fun visitCompanionObject(name: String) { + companionObjectName = name + } + + override fun visitConstructor(flags: Flags): KmConstructorVisitor? { + return if (Flag.Constructor.IS_PRIMARY(flags)) { + object : KmConstructorVisitor() { + val params = mutableListOf() + override fun visitValueParameter(flags: Flags, name: String): KmValueParameterVisitor? { + return object : KmValueParameterVisitor() { + override fun visitType(flags: Flags): KmTypeVisitor? { + return TypeNameKmTypeVisitor(flags, typeParamResolver) { + params += ParameterData(flags, name, it) + } + } + + override fun visitVarargElementType(flags: Flags): KmTypeVisitor? { + return TypeNameKmTypeVisitor(flags, typeParamResolver) { + params += ParameterData(flags, name, it, true) + } + } + } + } + + override fun visitEnd() { + constructorData = ConstructorData(flags, params) + } + } + } else { + null + } + } + + override fun visitSupertype(flags: Flags): KmTypeVisitor? { + return TypeNameKmTypeVisitor(flags, typeParamResolver) { + superTypes += it + } + } + + override fun visitProperty(flags: Flags, + name: String, + getterFlags: Flags, + setterFlags: Flags): KmPropertyVisitor? { + return object : KmPropertyVisitor() { + lateinit var type: TypeName + override fun visitEnd() { + properties += PropertyData(flags, name, type) + } + + override fun visitReturnType(flags: Flags): KmTypeVisitor? { + return TypeNameKmTypeVisitor(flags, typeParamResolver) { + type = it + } + } + } + } + }) + + return ClassData(className.replace("/", "."), + classFlags, + companionObjectName, + constructorData, + superTypes, + typeParameters.values.toList(), + properties) +} + +internal data class ClassData( + val name: String, + val flags: Flags, + val companionObjectName: String?, + val constructorData: ConstructorData?, + val superTypes: MutableList, + val typeVariables: List, + val properties: List +) { + fun getPropertyOrNull(methodElement: ExecutableElement): PropertyData? { + return methodElement.simpleName.toString() + .takeIf { it.endsWith(kotlinPropertyAnnotationsFunPostfix) } + ?.substringBefore(kotlinPropertyAnnotationsFunPostfix) + ?.let { propertyName -> properties.firstOrNull { propertyName == it.name } } + } + + companion object { + /** + * Postfix of the method name containing the [kotlin.Metadata] annotation for the relative property. + * @see [getPropertyOrNull] + */ + const val kotlinPropertyAnnotationsFunPostfix = "\$annotations" + } +} + +internal data class ConstructorData( + val flags: Flags, + val parameters: List +) + +internal data class ParameterData( + val flags: Flags, + val name: String, + val type: TypeName, + val isVarArg: Boolean = false +) { + val declaresDefaultValue = Flag.ValueParameter.DECLARES_DEFAULT_VALUE(flags) +} + +internal data class PropertyData( + val flags: Flags, + val name: String, + val type: TypeName +) { + val hasSetter = Flag.Property.HAS_SETTER(flags) +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..01b8bf6 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c483221 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,22 @@ +# +# Copyright (c) 2018 Zac Sweers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +#Wed Jun 06 23:25:02 PDT 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/sample/build.gradle b/sample/build.gradle new file mode 100644 index 0000000..f4bbfef --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Zac Sweers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +buildscript { + ext.kotlin_version = '1.2.41' + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'org.jetbrains.kotlin.jvm' +apply plugin: 'org.jetbrains.kotlin.kapt' + +repositories { + mavenCentral() + maven { + url "https://kotlin.bintray.com/kotlinx" + } +} + +dependencies { + kapt project(':copydynamic') + compile project(':copydynamic-annotations') + + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" +} + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = ['-Xjsr305=strict'] + } +} diff --git a/sample/src/main/kotlin/io/sweers/copydynamic/sample/Foo.kt b/sample/src/main/kotlin/io/sweers/copydynamic/sample/Foo.kt new file mode 100644 index 0000000..4fecd04 --- /dev/null +++ b/sample/src/main/kotlin/io/sweers/copydynamic/sample/Foo.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 Zac Sweers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sweers.copydynamic.sample + +import io.sweers.copydynamic.annotations.CopyDynamic + +@CopyDynamic +data class Foo(val bar: String = "bar", val baz: String = "baz", val fizz: String = "fizz") diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..121d5ed --- /dev/null +++ b/settings.gradle @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2018 Zac Sweers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +rootProject.name = 'copydynamic-root' +include 'copydynamic' +include 'copydynamic-annotations' +include 'sample'