Skip to content

Commit

Permalink
Multiple improvements and additions
Browse files Browse the repository at this point in the history
  • Loading branch information
Takhion committed Sep 10, 2017
1 parent 992f476 commit 9891a4b
Show file tree
Hide file tree
Showing 17 changed files with 451 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import me.eugeniomarletti.Generator.Input
import me.eugeniomarletti.Generator.Parameter
import me.eugeniomarletti.Generator.TypeParameter
import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
import me.eugeniomarletti.kotlin.metadata.KotlinMetadataUtils
import me.eugeniomarletti.kotlin.metadata.extractFullName
import me.eugeniomarletti.kotlin.metadata.isDataClass
import me.eugeniomarletti.kotlin.metadata.isPrimary
import me.eugeniomarletti.kotlin.metadata.kaptGeneratedOption
import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
import me.eugeniomarletti.kotlin.processing.KotlinAbstractProcessor
import org.jetbrains.kotlin.serialization.ProtoBuf
import java.io.File
import javax.annotation.processing.AbstractProcessor
Expand All @@ -22,16 +24,16 @@ import javax.tools.Diagnostic.Kind.ERROR

@AutoService(Processor::class)
@Suppress("unused")
class DataClassWithProcessor : AbstractProcessor() {
class DataClassWithProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils {

private val annotationName = WithMethods::class.java.canonicalName

override fun getSupportedAnnotationTypes() = setOf(annotationName)

override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()

override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
val annotationElement = processingEnv.elementUtils.getTypeElement(annotationName)
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
val annotationElement = elementUtils.getTypeElement(annotationName)
@Suppress("LoopToCallChain")
for (element in roundEnv.getElementsAnnotatedWith(annotationElement)) {
val input = getInputFrom(element) ?: continue
Expand Down Expand Up @@ -89,18 +91,18 @@ class DataClassWithProcessor : AbstractProcessor() {
}

private fun errorMustBeDataClass(element: Element) {
processingEnv.messager.printMessage(ERROR,
messager.printMessage(ERROR,
"@${WithMethods::class.java.simpleName} can't be applied to $element: must be a Kotlin data class", element)
}

private fun Input.generateAndWrite(): Boolean {
val generateOutput = processingEnv.options[kaptGeneratedOption]?.let(::File) ?: run {
processingEnv.messager.printMessage(ERROR, "Can't find option '$kaptGeneratedOption'")
val generatedDir = generatedDir ?: run {
messager.printMessage(ERROR, "Can't find option '$kaptGeneratedOption'")
return false
}
val dirPath = `package`.replace('.', File.separatorChar)
val filePath = "DataClassWithExtensions_${fqClassName.substringAfter(`package`).replace('.', '_')}.kt"
val dir = File(generateOutput, dirPath).also { it.mkdirs() }
val dir = File(generatedDir, dirPath).also { it.mkdirs() }
val file = File(dir, filePath)
file.writeText(generate())
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ internal object Generator {
private fun whereClause(typeArguments: List<TypeParameter>) =
typeArguments
.flatMap { (name, upperBounds) -> upperBounds.map { Pair(name, it) } }
.filterNot { (_, upperBound) -> upperBound.isNullOrBlank() }
.filterNot { (_, upperBound) -> upperBound.isBlank() }
.takeIf { it.isNotEmpty() }
?.joinToString(prefix = "\n where ") { (name, upperBound) -> "$name : $upperBound" }
?: ""
Expand Down

This file was deleted.

14 changes: 12 additions & 2 deletions lib/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
ext {
PUBLISH_GROUP_ID = 'me.eugeniomarletti'
PUBLISH_ARTIFACT_ID = 'kotlin-metadata'
PUBLISH_VERSION = '1.2.0'
}

apply plugin: 'kotlin'

dependencies {
Expand All @@ -8,6 +14,10 @@ dependencies {
}

compileKotlin {
//TODO remove after youtrack.jetbrains.com/issue/KT-16780
kotlinOptions.freeCompilerArgs += ["-Xskip-runtime-version-check"]
kotlinOptions {
freeCompilerArgs = ['-module-name', "$PUBLISH_GROUP_ID.$PUBLISH_ARTIFACT_ID"]
//TODO remove after youtrack.jetbrains.com/issue/KT-16780
freeCompilerArgs += ["-Xskip-runtime-version-check"]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

package me.eugeniomarletti.kotlin.metadata

/**
* Name of the processor option containing the path to the Kotlin generated src dir.
*/
const val kaptGeneratedOption = "kapt.kotlin.generated"

/**
* Fully qualified name class name of [kotlin.Metadata] (which is internal).
*/
const val kotlinMetadataAnnotation = "kotlin.Metadata"

/**
* Postfix of the method name containing the [kotlin.Metadata] annotation for the relative property.
* @see [getPropertyOrNull]
*/
const val kotlinPropertyAnnotationsFunPostfix = "\$annotations"
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private val ProtoBuf.Function.flagsOrOld: Int get() = if (hasFlags()) flags else
private val ProtoBuf.Property.flagsOrOld: Int get() = if (hasFlags()) flags else loadOldFlags(oldFlags)

/**
* @see MemberDeserializer.loadOldFlags
* @see [MemberDeserializer.loadOldFlags]
*/
private fun loadOldFlags(oldFlags: Int): Int {
val lowSixBits = oldFlags and 0x3f
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ internal val Element.kotlinClassHeader: KotlinClassHeader? get() {
)
}

private fun unwrapAnnotationValue(value: Any?): Any? =
private tailrec fun unwrapAnnotationValue(value: Any?): Any? =
when (value) {
is AnnotationValue -> unwrapAnnotationValue(value.value)
is List<*> -> value.map(::unwrapAnnotationValue)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package me.eugeniomarletti.kotlin.metadata

import me.eugeniomarletti.kotlin.metadata.jvm.JvmDescriptorUtils
import me.eugeniomarletti.kotlin.metadata.jvm.getJvmMethodSignature
import org.jetbrains.kotlin.serialization.ClassData
import org.jetbrains.kotlin.serialization.PackageData
import org.jetbrains.kotlin.serialization.ProtoBuf
import org.jetbrains.kotlin.serialization.deserialization.NameResolver
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.VariableElement

/**
* Main repository for extensions that need to access stuff inside [ProcessingEnvironment].
*/
interface KotlinMetadataUtils : JvmDescriptorUtils {

/**
* Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`.
*
* Useful for comparing with [ProtoBuf.Function.getJvmMethodSignature][getJvmMethodSignature].
*
* For reference, see the [JVM specification, section 4.3](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
*/
val ExecutableElement.jvmMethodSignature: String
get() = "$simpleName${asType().descriptor}"

/**
* If possible, returns the [ProtoBuf.Function] inside [functionList] represented by [methodElement].
*/
fun getFunctionOrNull(
methodElement: ExecutableElement,
nameResolver: NameResolver,
functionList: List<ProtoBuf.Function>
): ProtoBuf.Function? =
methodElement.jvmMethodSignature.let { methodSignature ->
functionList.firstOrNull { methodSignature == it.getJvmMethodSignature(nameResolver) }
}

/** @see [getFunctionOrNull] */
fun ClassData.getFunctionOrNull(methodElement: ExecutableElement) =
getFunctionOrNull(methodElement, nameResolver, proto.functionList)

/** @see [getFunctionOrNull] */
fun PackageData.getFunctionOrNull(methodElement: ExecutableElement) =
getFunctionOrNull(methodElement, nameResolver, proto.functionList)
}

/**
* If this [isNotBlank] then it adds the optional [prefix] and [postfix].
*/
fun String.plusIfNotBlank(
prefix: String = "",
postfix: String = ""
) =
if (isNotBlank()) "$prefix${this}$postfix" else this

/**
* Returns the escaped "readable" version of the internal Kotlin class name.
*/
val String.escapedClassName
get() = split('/', '.').joinToString("`.`").plusIfNotBlank(prefix = "`", postfix = "`")

/**
* Same as [ClassData.classProto], useful while copy/pasting when duplicating extensions for [PackageData].
*/
inline val ClassData.proto get() = classProto

/**
* Same as [PackageData.packageProto], useful while copy/pasting when duplicating extensions for [ClassData].
*/
inline val PackageData.proto get() = packageProto

/**
* If possible, returns the [ProtoBuf.Property] inside [propertyList] represented by [methodElement].
*/
inline fun getPropertyOrNull(
methodElement: ExecutableElement,
nameResolver: NameResolver,
propertyList: () -> List<ProtoBuf.Property>
): ProtoBuf.Property? =
methodElement.simpleName.toString()
.takeIf { it.endsWith(kotlinPropertyAnnotationsFunPostfix) }
?.substringBefore(kotlinPropertyAnnotationsFunPostfix)
?.let { propertyName -> propertyList().firstOrNull { propertyName == nameResolver.getString(it.name) } }

/** @see [getPropertyOrNull] */
fun ClassData.getPropertyOrNull(methodElement: ExecutableElement) =
getPropertyOrNull(methodElement, nameResolver, proto::getPropertyList)

/** @see [getPropertyOrNull] */
fun PackageData.getPropertyOrNull(methodElement: ExecutableElement) =
getPropertyOrNull(methodElement, nameResolver, proto::getPropertyList)

/**
* If possible, returns the [ProtoBuf.ValueParameter] inside [function] represented by [parameterElement].
*/
fun getValueParameterOrNull(
nameResolver: NameResolver,
function: ProtoBuf.Function,
parameterElement: VariableElement
): ProtoBuf.ValueParameter? =
parameterElement.simpleName.toString().let { parameterName ->
function.valueParameterList.firstOrNull { parameterName == nameResolver.getString(it.name) }
}

/** @see [getValueParameterOrNull] */
fun ClassData.getValueParameterOrNull(function: ProtoBuf.Function, parameterElement: VariableElement) =
getValueParameterOrNull(nameResolver, function, parameterElement)

/** @see [getValueParameterOrNull] */
fun PackageData.getValueParameterOrNull(function: ProtoBuf.Function, parameterElement: VariableElement) =
getValueParameterOrNull(nameResolver, function, parameterElement)

/**
* Returns the fully qualified name of this type as it would be seen in the source code, including nullability and generic type parameters.
*
* Package and class names are escaped with backticks through [escapedClassName].
*
* @param [getTypeParameter]
* A function that returns the type parameter for the given index.
* **Only called if [ProtoBuf.Type.hasTypeParameter] is `true`!**
*
* @param [outputTypeAlias]
* If `true` type aliases will be used, otherwise they will be replaced by the concrete type.
*
* @param [throwOnGeneric]
* If not `null` it will be thrown if this type contains generic information.
*/
fun ProtoBuf.Type.extractFullName(
nameResolver: NameResolver,
getTypeParameter: (index: Int) -> ProtoBuf.TypeParameter,
outputTypeAlias: Boolean = true,
throwOnGeneric: Throwable? = null
): String {

if (!hasClassName() && throwOnGeneric != null) throw throwOnGeneric

val name = when {
hasTypeParameter() -> getTypeParameter(typeParameter).name
hasTypeParameterName() -> typeParameterName
outputTypeAlias && hasAbbreviatedType() -> abbreviatedType.typeAliasName
else -> className
}.let { nameResolver.getString(it).escapedClassName }

val argumentList = when {
outputTypeAlias && hasAbbreviatedType() -> abbreviatedType.argumentList
else -> argumentList
}
val arguments = argumentList
.takeIf { it.isNotEmpty() }
?.joinToString(prefix = "<", postfix = ">") {
when {
it.hasType() -> it.type.extractFullName(nameResolver, getTypeParameter, outputTypeAlias, throwOnGeneric)
throwOnGeneric != null -> throw throwOnGeneric
else -> "*"
}
}
?: ""

val nullability = if (nullable) "?" else ""

return name + arguments + nullability
}

/** @see [extractFullName] */
fun ProtoBuf.Type.extractFullName(
data: ClassData,
outputTypeAlias: Boolean = true,
throwOnGeneric: Throwable? = null
) =
extractFullName(data.nameResolver, data.proto::getTypeParameter, outputTypeAlias, throwOnGeneric)

/** @see [extractFullName] */
fun ProtoBuf.Type.extractFullName(
data: PackageData,
outputTypeAlias: Boolean = true,
throwOnGeneric: Throwable? = null
) =
extractFullName(data.nameResolver, { throw IllegalStateException() }, outputTypeAlias, throwOnGeneric)
Loading

0 comments on commit 9891a4b

Please sign in to comment.