From ca9c260e91b15a59bea4556de9567470f144de1a Mon Sep 17 00:00:00 2001 From: Marcel Schnelle Date: Sat, 6 Apr 2019 19:40:59 +0200 Subject: [PATCH] Add support for internal classes (#606) * 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 --- gradle.properties | 5 +- processor/build.gradle | 1 + .../processor/PermissionsProcessor.kt | 3 +- .../processor/RuntimePermissionsElement.kt | 22 ++--- .../impl/java/JavaBaseProcessorUnit.kt | 2 +- .../impl/kotlin/KotlinBaseProcessorUnit.kt | 11 +++ .../dispatcher/processor/util/Helpers.kt | 42 +++++--- .../processor/KtProcessorTestSuite.kt | 95 +++++++++++++++++++ 8 files changed, 151 insertions(+), 30 deletions(-) diff --git a/gradle.properties b/gradle.properties index d08ba8d5..e674928d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/processor/build.gradle b/processor/build.gradle index 9a5bb322..ef40598c 100644 --- a/processor/build.gradle +++ b/processor/build.gradle @@ -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') diff --git a/processor/src/main/kotlin/permissions/dispatcher/processor/PermissionsProcessor.kt b/processor/src/main/kotlin/permissions/dispatcher/processor/PermissionsProcessor.kt index deee754d..a9f074f2 100644 --- a/processor/src/main/kotlin/permissions/dispatcher/processor/PermissionsProcessor.kt +++ b/processor/src/main/kotlin/permissions/dispatcher/processor/PermissionsProcessor.kt @@ -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 @@ -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 { diff --git a/processor/src/main/kotlin/permissions/dispatcher/processor/RuntimePermissionsElement.kt b/processor/src/main/kotlin/permissions/dispatcher/processor/RuntimePermissionsElement.kt index 208ba433..4cefe8ad 100644 --- a/processor/src/main/kotlin/permissions/dispatcher/processor/RuntimePermissionsElement.kt +++ b/processor/src/main/kotlin/permissions/dispatcher/processor/RuntimePermissionsElement.kt @@ -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() diff --git a/processor/src/main/kotlin/permissions/dispatcher/processor/impl/java/JavaBaseProcessorUnit.kt b/processor/src/main/kotlin/permissions/dispatcher/processor/impl/java/JavaBaseProcessorUnit.kt index 83b2b760..ada0e14d 100644 --- a/processor/src/main/kotlin/permissions/dispatcher/processor/impl/java/JavaBaseProcessorUnit.kt +++ b/processor/src/main/kotlin/permissions/dispatcher/processor/impl/java/JavaBaseProcessorUnit.kt @@ -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()) diff --git a/processor/src/main/kotlin/permissions/dispatcher/processor/impl/kotlin/KotlinBaseProcessorUnit.kt b/processor/src/main/kotlin/permissions/dispatcher/processor/impl/kotlin/KotlinBaseProcessorUnit.kt index 2ec779dc..5422ed83 100644 --- a/processor/src/main/kotlin/permissions/dispatcher/processor/impl/kotlin/KotlinBaseProcessorUnit.kt +++ b/processor/src/main/kotlin/permissions/dispatcher/processor/impl/kotlin/KotlinBaseProcessorUnit.kt @@ -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()) } @@ -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] @@ -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] diff --git a/processor/src/main/kotlin/permissions/dispatcher/processor/util/Helpers.kt b/processor/src/main/kotlin/permissions/dispatcher/processor/util/Helpers.kt index e1dae642..377d23bb 100644 --- a/processor/src/main/kotlin/permissions/dispatcher/processor/util/Helpers.kt +++ b/processor/src/main/kotlin/permissions/dispatcher/processor/util/Helpers.kt @@ -2,6 +2,12 @@ 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 @@ -9,20 +15,6 @@ 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? by lazy { - try { - @Suppress("UNCHECKED_CAST") - Class.forName("kotlin.Metadata") as Class - } 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()) @@ -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" diff --git a/processor/src/test/java/permissions/dispatcher/processor/KtProcessorTestSuite.kt b/processor/src/test/java/permissions/dispatcher/processor/KtProcessorTestSuite.kt index 234bbd0f..82aa91be 100644 --- a/processor/src/test/java/permissions/dispatcher/processor/KtProcessorTestSuite.kt +++ b/processor/src/test/java/permissions/dispatcher/processor/KtProcessorTestSuite.kt @@ -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 = arrayOf("android.permission.CAMERA") + + private const val REQUEST_SYSTEMALERTWINDOW: Int = 1 + + private val PERMISSION_SYSTEMALERTWINDOW: Array = + 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) + } }