Skip to content

Commit

Permalink
Add support for internal classes (#606)
Browse files Browse the repository at this point in the history
* Bump to Kotlin 1.3 to expose Metadata annotation directly

With this, we can more easily distinguish between Java & Kotlin
without the previous Class.forName() lookup in Helpers.kt

* Add an extension property to determine internal modifier

* Insert conditional logic to restrict method visibility of generated code

* Add code generation test for internal class
  • Loading branch information
mannodermaus authored and hotchemi committed Apr 6, 2019
1 parent 3df0667 commit ca9c260
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 30 deletions.
5 changes: 3 additions & 2 deletions gradle.properties
Expand Up @@ -13,8 +13,9 @@ LICENSES = ['Apache-2.0']

# Plugin versions
GRADLE_PLUGIN_VERSION = 3.2.1
KOTLIN_VERSION = 1.2.71
KOTLIN_COMPILER_LIB_VERSION = 1.3.11
KOTLIN_VERSION = 1.3.21
KOTLIN_COMPILER_LIB_VERSION = 1.3.21
KOTLIN_METADATA_VERSION = 0.0.5
CONFIG_PLUGIN_VERSION = 2.2.2
JFROG_PLUGIN_VERSION = 4.1.1
BINTRAY_PLUGIN_VERSION = 1.8.4
Expand Down
1 change: 1 addition & 0 deletions processor/build.gradle
Expand Up @@ -29,6 +29,7 @@ artifacts {
dependencies {
compile "androidx.annotation:annotation:$ANDROIDX_LIBRARY_VERSION"
compile "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
compile "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$KOTLIN_METADATA_VERSION"
compile "com.squareup:javapoet:$JAVAPOET_VERSION"
compile "com.squareup:kotlinpoet:$KOTLINPOET_VERSION"
compile project(path: ':annotation')
Expand Down
Expand Up @@ -4,7 +4,6 @@ import permissions.dispatcher.RuntimePermissions
import permissions.dispatcher.processor.impl.javaProcessorUnits
import permissions.dispatcher.processor.impl.kotlinProcessorUnits
import permissions.dispatcher.processor.util.findAndValidateProcessorUnit
import permissions.dispatcher.processor.util.kotlinMetadataClass
import java.io.File
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Filer
Expand Down Expand Up @@ -58,7 +57,7 @@ class PermissionsProcessor : AbstractProcessor() {
.sortedBy { it.simpleName.toString() }
.forEach {
val rpe = RuntimePermissionsElement(it as TypeElement)
val kotlinMetadata = it.getAnnotation(kotlinMetadataClass)
val kotlinMetadata = it.getAnnotation(Metadata::class.java)
if (kotlinMetadata != null) {
processKotlin(it, rpe, requestCodeProvider)
} else {
Expand Down
Expand Up @@ -12,18 +12,18 @@ import permissions.dispatcher.processor.util.*
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement

class RuntimePermissionsElement(val e: TypeElement) {
val typeName: TypeName = TypeName.get(e.asType())
val ktTypeName = e.asType().asTypeName()
val typeVariables = e.typeParameters.map { TypeVariableName.get(it) }
val ktTypeVariables = e.typeParameters.map { it.asTypeVariableName() }
val packageName = e.packageName()
val inputClassName = e.simpleString()
class RuntimePermissionsElement(val element: TypeElement) {
val typeName: TypeName = TypeName.get(element.asType())
val ktTypeName = element.asType().asTypeName()
val typeVariables = element.typeParameters.map { TypeVariableName.get(it) }
val ktTypeVariables = element.typeParameters.map { it.asTypeVariableName() }
val packageName = element.packageName()
val inputClassName = element.simpleString()
val generatedClassName = inputClassName + GEN_CLASS_SUFFIX
val needsElements = e.childElementsAnnotatedWith(NeedsPermission::class.java)
private val onRationaleElements = e.childElementsAnnotatedWith(OnShowRationale::class.java)
private val onDeniedElements = e.childElementsAnnotatedWith(OnPermissionDenied::class.java)
private val onNeverAskElements = e.childElementsAnnotatedWith(OnNeverAskAgain::class.java)
val needsElements = element.childElementsAnnotatedWith(NeedsPermission::class.java)
private val onRationaleElements = element.childElementsAnnotatedWith(OnShowRationale::class.java)
private val onDeniedElements = element.childElementsAnnotatedWith(OnPermissionDenied::class.java)
private val onNeverAskElements = element.childElementsAnnotatedWith(OnNeverAskAgain::class.java)

init {
validateNeedsMethods()
Expand Down
Expand Up @@ -47,7 +47,7 @@ abstract class JavaBaseProcessorUnit : JavaProcessorUnit {

private fun createTypeSpec(rpe: RuntimePermissionsElement, requestCodeProvider: RequestCodeProvider): TypeSpec {
return TypeSpec.classBuilder(rpe.generatedClassName)
.addOriginatingElement(rpe.e) // for incremental annotation processing
.addOriginatingElement(rpe.element) // for incremental annotation processing
.addModifiers(Modifier.FINAL)
.addFields(createFields(rpe, requestCodeProvider))
.addMethod(createConstructor())
Expand Down
Expand Up @@ -110,6 +110,9 @@ abstract class KotlinBaseProcessorUnit : KtProcessorUnit {
val builder = FunSpec.builder(withPermissionCheckMethodName(method))
.addTypeVariables(rpe.ktTypeVariables)
.receiver(rpe.ktTypeName)
if (method.enclosingElement.isInternal) {
builder.addModifiers(KModifier.INTERNAL)
}
method.parameters.forEach {
builder.addParameter(it.simpleString(), it.asPreparedType())
}
Expand Down Expand Up @@ -255,6 +258,10 @@ abstract class KotlinBaseProcessorUnit : KtProcessorUnit {
.receiver(rpe.ktTypeName)
.addParameter(requestCodeParam, INT)

if (rpe.element.isInternal) {
builder.addModifiers(KModifier.INTERNAL)
}

builder.beginControlFlow("when (%N)", requestCodeParam)
for (needsMethod in rpe.needsElements) {
val needsPermissionParameter = needsMethod.getAnnotation(NeedsPermission::class.java).value[0]
Expand All @@ -279,6 +286,10 @@ abstract class KotlinBaseProcessorUnit : KtProcessorUnit {
.addParameter(requestCodeParam, INT)
.addParameter(grantResultsParam, INT_ARRAY)

if (rpe.element.isInternal) {
builder.addModifiers(KModifier.INTERNAL)
}

builder.beginControlFlow("when (%N)", requestCodeParam)
for (needsMethod in rpe.needsElements) {
val needsPermissionParameter = needsMethod.getAnnotation(NeedsPermission::class.java).value[0]
Expand Down
Expand Up @@ -2,27 +2,19 @@ package permissions.dispatcher.processor.util

import com.squareup.javapoet.CodeBlock
import com.squareup.javapoet.TypeName
import kotlinx.metadata.ClassName
import kotlinx.metadata.Flag
import kotlinx.metadata.Flags
import kotlinx.metadata.KmClassVisitor
import kotlinx.metadata.jvm.KotlinClassHeader
import kotlinx.metadata.jvm.KotlinClassMetadata
import permissions.dispatcher.NeedsPermission
import permissions.dispatcher.processor.ELEMENT_UTILS
import permissions.dispatcher.processor.RuntimePermissionsElement
import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.type.TypeMirror

/**
* Class Reference to the kotlin.Metadata annotation class,
* used by the processor to tell apart Kotlin from Java files during code generation.
*/
val kotlinMetadataClass: Class<Annotation>? by lazy {
try {
@Suppress("UNCHECKED_CAST")
Class.forName("kotlin.Metadata") as Class<Annotation>
} catch (e: Throwable) {
// Java-only environment, or outdated Kotlin version
null
}
}

fun typeMirrorOf(className: String): TypeMirror = ELEMENT_UTILS.getTypeElement(className).asType()

fun typeNameOf(it: Element): TypeName = TypeName.get(it.asType())
Expand All @@ -35,6 +27,28 @@ fun pendingRequestFieldName(e: ExecutableElement) = "$GEN_PENDING_PREFIX${e.simp

fun withPermissionCheckMethodName(e: ExecutableElement) = "${e.simpleString().trimDollarIfNeeded()}$GEN_WITH_PERMISSION_CHECK_SUFFIX"

fun Element.kotlinMetadata(): KotlinClassMetadata? =
getAnnotation(Metadata::class.java)?.run {
KotlinClassMetadata.read(KotlinClassHeader(kind, metadataVersion, bytecodeVersion, data1, data2, extraString, packageName, extraInt))
}

val Element.isInternal: Boolean
get() {
val classMetadata = kotlinMetadata()
as? KotlinClassMetadata.Class
?: return false

var returnValue = false
classMetadata.accept(object : KmClassVisitor() {
override fun visit(flags: Flags, name: ClassName) {
if (Flag.IS_INTERNAL(flags)) {
returnValue = true
}
}
})
return returnValue
}

fun ExecutableElement.argumentFieldName(arg: Element) = "${simpleString()}${arg.simpleString().capitalize()}"

fun ExecutableElement.proceedOnShowRationaleMethodName() = "proceed${simpleString().trimDollarIfNeeded().capitalize()}$GEN_PERMISSION_REQUEST_SUFFIX"
Expand Down
Expand Up @@ -621,4 +621,99 @@ fun MyActivity.onRequestPermissionsResult(requestCode: Int, grantResults: IntArr
.generatedFile("MyActivityPermissionsDispatcher.kt")
.hasSourceEquivalentTo(expected)
}

@Test
fun validInternalFragment() {
@Language("kotlin") val source = """
import android.Manifest
import androidx.fragment.app.Fragment
import permissions.dispatcher.RuntimePermissions
import permissions.dispatcher.NeedsPermission
@RuntimePermissions
internal class MyFragment : Fragment {
@NeedsPermission(Manifest.permission.CAMERA)
fun showCamera() {
}
@NeedsPermission(Manifest.permission.SYSTEM_ALERT_WINDOW)
fun systemAlertWindow() {
}
}
""".trimIndent()

@Language("kotlin") val expected = """
// This file was generated by PermissionsDispatcher. Do not modify!
@file:JvmName("MyFragmentPermissionsDispatcher")
import android.content.Intent
import android.net.Uri
import android.provider.Settings
import kotlin.Array
import kotlin.Int
import kotlin.IntArray
import kotlin.String
import permissions.dispatcher.PermissionUtils
private const val REQUEST_SHOWCAMERA: Int = 0
private val PERMISSION_SHOWCAMERA: Array<String> = arrayOf("android.permission.CAMERA")
private const val REQUEST_SYSTEMALERTWINDOW: Int = 1
private val PERMISSION_SYSTEMALERTWINDOW: Array<String> =
arrayOf("android.permission.SYSTEM_ALERT_WINDOW")
internal fun MyFragment.showCameraWithPermissionCheck() {
if (PermissionUtils.hasSelfPermissions(this.requireActivity(), *PERMISSION_SHOWCAMERA)) {
showCamera()
} else {
this.requestPermissions(PERMISSION_SHOWCAMERA, REQUEST_SHOWCAMERA)
}
}
internal fun MyFragment.systemAlertWindowWithPermissionCheck() {
if (PermissionUtils.hasSelfPermissions(this.requireActivity(), *PERMISSION_SYSTEMALERTWINDOW) ||
Settings.canDrawOverlays(this.requireActivity())) {
systemAlertWindow()
} else {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" +
this.requireActivity().getPackageName()))
this.startActivityForResult(intent, REQUEST_SYSTEMALERTWINDOW)
}
}
internal fun MyFragment.onRequestPermissionsResult(requestCode: Int, grantResults: IntArray) {
when (requestCode) {
REQUEST_SHOWCAMERA ->
{
if (PermissionUtils.verifyPermissions(*grantResults)) {
showCamera()
}
}
}
}
internal fun MyFragment.onActivityResult(requestCode: Int) {
when (requestCode) {
REQUEST_SYSTEMALERTWINDOW -> {
if (PermissionUtils.hasSelfPermissions(this.requireActivity(),
*PERMISSION_SYSTEMALERTWINDOW) ||
Settings.canDrawOverlays(this.requireActivity())) {
systemAlertWindow()
}
}
}
}
""".trimIndent()

kotlinc()
.withProcessors(PermissionsProcessor())
.addKotlin("sources.kt", source)
.compile()
.succeededWithoutWarnings()
.generatedFile("MyFragmentPermissionsDispatcher.kt")
.hasSourceEquivalentTo(expected)
}
}

0 comments on commit ca9c260

Please sign in to comment.