mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
feat: use Kotlin Symbol Processor to generate IViewAdapterIndex implementation
This commit is contained in:
Generated
+7
-1
@@ -5,15 +5,18 @@
|
||||
<profile name="Gradle Imported" enabled="true">
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.auto.value/auto-value/1.10/e97ada61b651d7dff21931c2cc1b158e870cb417/auto-value-1.10.jar" />
|
||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.auto.value/auto-value/1.10.1/e0184c26c3a20257c6fb46eaea16fdd3d7b6d357/auto-value-1.10.1.jar" />
|
||||
</processorPath>
|
||||
<module name="AndroidIDE.subprojects.google-java-format.main" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel target="1.8">
|
||||
<module name="AndroidIDE.annotation-processors" target="11" />
|
||||
<module name="AndroidIDE.annotation-processors-java" target="11" />
|
||||
<module name="AndroidIDE.annotation-processors-java.main" target="11" />
|
||||
<module name="AndroidIDE.annotation-processors-java.test" target="11" />
|
||||
<module name="AndroidIDE.annotation-processors.main" target="11" />
|
||||
<module name="AndroidIDE.annotation-processors.test" target="11" />
|
||||
<module name="AndroidIDE.build-tools.aaptcompiler" target="11" />
|
||||
<module name="AndroidIDE.build-tools.aaptcompiler.test" target="11" />
|
||||
<module name="AndroidIDE.build-tools.builder-api" target="11" />
|
||||
@@ -92,6 +95,9 @@
|
||||
<module name="AndroidIDE.gradle-tooling" target="1.8" />
|
||||
<module name="AndroidIDE.gradle-tooling.plugin" target="1.8" />
|
||||
<module name="AndroidIDE.javac" target="1.8" />
|
||||
<module name="AndroidIDE.ksp-processor" target="11" />
|
||||
<module name="AndroidIDE.ksp-processor.main" target="11" />
|
||||
<module name="AndroidIDE.ksp-processor.test" target="11" />
|
||||
<module name="AndroidIDE.sdk-info" target="11" />
|
||||
<module name="AndroidIDE.treesitter.test" target="1.7" />
|
||||
</bytecodeTargetLevel>
|
||||
|
||||
@@ -15,21 +15,23 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@Suppress("JavaPluginLanguageLevel")
|
||||
plugins {
|
||||
id("java-library")
|
||||
id("kotlin")
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
|
||||
group = "com.itsaky.androidide.annotations"
|
||||
version = Versions.versionName
|
||||
|
||||
dependencies {
|
||||
implementation(libs.common.jkotlin)
|
||||
implementation(libs.common.javapoet)
|
||||
implementation(libs.google.auto.service)
|
||||
|
||||
implementation(kotlin("stdlib"))
|
||||
|
||||
implementation(projects.annotations)
|
||||
implementation(projects.shared)
|
||||
|
||||
kapt(libs.google.auto.service)
|
||||
kapt(projects.subprojects.frameworkStubs)
|
||||
|
||||
implementation(libs.common.javapoet)
|
||||
implementation(libs.common.ksp)
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
java.srcDirs("src/main/kotlin")
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.annotations.ksp
|
||||
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||
import com.itsaky.androidide.annotations.ksp.inflater.ViewAdapterSymbolProcessor
|
||||
|
||||
/**
|
||||
* AndroidIDE [SymbolProcessorProvider] implementation.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class IDESymbolProcessorProvider : SymbolProcessorProvider {
|
||||
|
||||
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
||||
return ViewAdapterSymbolProcessor(environment.codeGenerator, environment.logger)
|
||||
}
|
||||
}
|
||||
+74
-97
@@ -15,8 +15,16 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.annotations.processor
|
||||
package com.itsaky.androidide.annotations.ksp.inflater
|
||||
|
||||
import com.google.devtools.ksp.KspExperimental
|
||||
import com.google.devtools.ksp.getAnnotationsByType
|
||||
import com.google.devtools.ksp.isAnnotationPresent
|
||||
import com.google.devtools.ksp.processing.KSPLogger
|
||||
import com.google.devtools.ksp.symbol.KSAnnotation
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSType
|
||||
import com.itsaky.androidide.annotations.inflater.ViewAdapter
|
||||
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
|
||||
import com.squareup.javapoet.ClassName
|
||||
@@ -26,37 +34,34 @@ import com.squareup.javapoet.JavaFile
|
||||
import com.squareup.javapoet.MethodSpec
|
||||
import com.squareup.javapoet.ParameterSpec
|
||||
import com.squareup.javapoet.ParameterizedTypeName
|
||||
import com.squareup.javapoet.TypeName
|
||||
import com.squareup.javapoet.TypeSpec
|
||||
import com.squareup.javapoet.WildcardTypeName
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
import java.io.OutputStreamWriter
|
||||
import java.util.Collections
|
||||
import javax.annotation.processing.ProcessingEnvironment
|
||||
import javax.lang.model.element.ExecutableElement
|
||||
import javax.lang.model.element.Modifier.FINAL
|
||||
import javax.lang.model.element.Modifier.PRIVATE
|
||||
import javax.lang.model.element.Modifier.PUBLIC
|
||||
import javax.lang.model.element.Modifier.STATIC
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.lang.model.type.MirroredTypeException
|
||||
import javax.tools.Diagnostic.Kind.ERROR
|
||||
|
||||
/**
|
||||
* Generates the ViewAdapterIndex class.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ViewAdapterIndexGenerator {
|
||||
class ViewAdapterIndexGenerator(private val logger: KSPLogger) {
|
||||
|
||||
private val indexClassBuilder =
|
||||
TypeSpec.classBuilder(INDEX_CLASS_NAME).addModifiers(PUBLIC, FINAL).addSuperinterface(viewAdapterInterface)
|
||||
TypeSpec.classBuilder(INDEX_CLASS_NAME)
|
||||
.addModifiers(PUBLIC, FINAL)
|
||||
.addSuperinterface(viewAdapterInterface)
|
||||
private val indexAddStatements = CodeBlock.builder()
|
||||
|
||||
fun init(env: ProcessingEnvironment) {
|
||||
fun init() {
|
||||
val adapterType =
|
||||
ParameterizedTypeName.get(
|
||||
ClassName.get(ADAPTER_BASE_CLASS_PCK, ADAPTER_BASE_CLASS_NAME),
|
||||
WildcardTypeName.subtypeOf(ClassName.get(env.viewElement()))
|
||||
WildcardTypeName.subtypeOf(viewClass)
|
||||
)
|
||||
val mapOfStringToViewAdapter =
|
||||
ParameterizedTypeName.get(
|
||||
@@ -77,7 +82,7 @@ class ViewAdapterIndexGenerator {
|
||||
)
|
||||
)
|
||||
|
||||
addPrivateConstructor(indexClassBuilder)
|
||||
// addPrivateConstructor(indexClassBuilder)
|
||||
|
||||
val indexImpl = ClassName.get(INDEX_PACKAGE_NAME, INDEX_CLASS_NAME)
|
||||
indexClassBuilder.addField(
|
||||
@@ -95,7 +100,7 @@ class ViewAdapterIndexGenerator {
|
||||
.returns(adapterType)
|
||||
.addStatement("var adapter = \$L.get(\$L)", INDEX_MAP_FIELD, "view")
|
||||
.beginControlFlow("if (adapter == null)")
|
||||
.addStatement("adapter = \$L.get(\$S)", INDEX_MAP_FIELD, env.viewElement().qualifiedName)
|
||||
.addStatement("adapter = \$L.get(\$S)", INDEX_MAP_FIELD, VIEW_CLASS)
|
||||
.endControlFlow()
|
||||
.addStatement("return adapter")
|
||||
.build()
|
||||
@@ -157,42 +162,54 @@ class ViewAdapterIndexGenerator {
|
||||
)
|
||||
}
|
||||
|
||||
private val listOfString: TypeName =
|
||||
ParameterizedTypeName.get(java.util.ArrayList::class.java, String::class.java)
|
||||
|
||||
fun addViewAdapter(processingEnv: ProcessingEnvironment, element: TypeElement) {
|
||||
@OptIn(KspExperimental::class)
|
||||
fun addViewAdapter(sym: KSClassDeclaration): Boolean {
|
||||
val block = CodeBlock.builder()
|
||||
|
||||
val viewAdapter =
|
||||
element.getAnnotation(ViewAdapter::class.java) ?: throw IllegalStateException()
|
||||
val includeInDesigner = element.getAnnotation(IncludeInDesigner::class.java)
|
||||
sym.getKSAnnotationsByType(ViewAdapter::class).iterator().run {
|
||||
val result = next()
|
||||
if (hasNext()) {
|
||||
logger.error("${ViewAdapter::class.simpleName} can only be applied once.", sym)
|
||||
return false
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
val viewName = getViewName(processingEnv, viewAdapter)
|
||||
val superclasses = getViewSuperclasses(processingEnv, viewAdapter)
|
||||
val includeInDesigner =
|
||||
if (sym.isAnnotationPresent(IncludeInDesigner::class)) {
|
||||
sym.getAnnotationsByType(IncludeInDesigner::class).iterator().run {
|
||||
val result = next()
|
||||
if (hasNext()) {
|
||||
logger.error("${IncludeInDesigner::class.simpleName} can only be applied once.", sym)
|
||||
return false
|
||||
}
|
||||
result
|
||||
}
|
||||
} else null
|
||||
|
||||
block.addStatement("final var superclasses = new \$T()", listOfString)
|
||||
for (superclass in superclasses) {
|
||||
block.addStatement("superclasses.add(\$S)", superclass)
|
||||
}
|
||||
val viewName = getViewName(viewAdapter)
|
||||
val moduleNs = getModuleNs(viewAdapter)
|
||||
|
||||
val adapterType = ClassName.get(element)
|
||||
val paramType = getViewTypeName(processingEnv, viewAdapter)
|
||||
val adapterTypeName = ClassName.get(sym.packageName.asString(), sym.simpleName.asString())
|
||||
val viewTypeName =
|
||||
ClassName.get(viewName.substringBeforeLast('.'), viewName.substringAfterLast('.'))
|
||||
|
||||
block.add("\n")
|
||||
block.addStatement(
|
||||
"final var adapter = new \$T()",
|
||||
ParameterizedTypeName.get(adapterType, paramType)
|
||||
ParameterizedTypeName.get(adapterTypeName, viewTypeName)
|
||||
)
|
||||
block.addStatement("adapter.\$L(superclasses)", METHOD_SET_SUPERCLASS_HIERARCHY)
|
||||
block.addStatement("adapter.\$L(\$S)", METHOD_SET_MODULE, viewAdapter.moduleNamespace)
|
||||
|
||||
block.addStatement("adapter.\$L(\$S)", METHOD_SET_MODULE, moduleNs)
|
||||
block.addStatement("adapters.put(\$S, adapter)", viewName)
|
||||
|
||||
if (includeInDesigner != null) {
|
||||
assertOverridesCreateWidget(processingEnv, element)
|
||||
assertOverridesCreateWidget(sym)
|
||||
block.addStatement(
|
||||
"providers.computeIfAbsent(\$T.\$L, g -> new ArrayList<>()).add(adapter)",
|
||||
"providers.computeIfAbsent(\$T.\$L, g -> new \$T<>()).add(adapter)",
|
||||
ClassName.get(IncludeInDesigner.Group::class.java),
|
||||
includeInDesigner.group
|
||||
includeInDesigner.group,
|
||||
ClassName.get(java.util.ArrayList::class.java)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -201,19 +218,11 @@ class ViewAdapterIndexGenerator {
|
||||
indexAddStatements.add(block.build())
|
||||
indexAddStatements.unindent()
|
||||
indexAddStatements.add("}\n")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun assertOverridesCreateWidget(env: ProcessingEnvironment, element: TypeElement) {
|
||||
element.enclosedElements.filterIsInstance<ExecutableElement>().find {
|
||||
ADAPTER_FUNC_CREATE_WIDGET == it.simpleName.toString()
|
||||
}
|
||||
?: env.messager.printMessage(
|
||||
ERROR,
|
||||
"${element.qualifiedName} annotated with ${IncludeInDesigner::class.java.simpleName} must override $ADAPTER_FUNC_CREATE_WIDGET"
|
||||
)
|
||||
}
|
||||
|
||||
fun generate(outDir: String) {
|
||||
fun generate(out: OutputStream) {
|
||||
val javaCollections = ClassName.get(Collections::class.java)
|
||||
|
||||
indexAddStatements.addStatement(
|
||||
@@ -229,61 +238,29 @@ class ViewAdapterIndexGenerator {
|
||||
)
|
||||
|
||||
indexClassBuilder.addStaticBlock(indexAddStatements.build())
|
||||
val file = JavaFile.builder(INDEX_PACKAGE_NAME, indexClassBuilder.build())
|
||||
val generatedDir = File(outDir)
|
||||
file.build().writeTo(generatedDir)
|
||||
}
|
||||
|
||||
private fun addPrivateConstructor(builder: TypeSpec.Builder) {
|
||||
builder.addMethod(MethodSpec.constructorBuilder().addModifiers(PRIVATE).build())
|
||||
}
|
||||
|
||||
private fun getViewTypeName(
|
||||
processingEnv: ProcessingEnvironment,
|
||||
annotation: ViewAdapter
|
||||
): TypeName {
|
||||
return try {
|
||||
ClassName.get(annotation.forView.java)
|
||||
?: throw IllegalStateException("Cannot find type of $annotation")
|
||||
} catch (err: MirroredTypeException) {
|
||||
(processingEnv.typeUtils.asElement(err.typeMirror) as TypeElement).let { ClassName.get(it) }
|
||||
|
||||
OutputStreamWriter(out).use {
|
||||
val file = JavaFile.builder(INDEX_PACKAGE_NAME, indexClassBuilder.build())
|
||||
file.build().writeTo(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getViewName(processingEnv: ProcessingEnvironment, annotation: ViewAdapter): String {
|
||||
return try {
|
||||
annotation.forView.qualifiedName
|
||||
?: throw IllegalStateException("Cannot find type of $annotation")
|
||||
} catch (err: MirroredTypeException) {
|
||||
(processingEnv.typeUtils.asElement(err.typeMirror) as TypeElement).qualifiedName.toString()
|
||||
}
|
||||
private fun assertOverridesCreateWidget(sym: KSClassDeclaration): Boolean {
|
||||
return sym.declarations
|
||||
.filter { it is KSFunctionDeclaration }
|
||||
.find { ADAPTER_FUNC_CREATE_WIDGET == it.simpleName.asString() } != null
|
||||
}
|
||||
|
||||
private fun getViewSuperclasses(
|
||||
processingEnv: ProcessingEnvironment,
|
||||
annotation: ViewAdapter
|
||||
): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
try {
|
||||
val view = annotation.forView
|
||||
var superclass = view.java.superclass
|
||||
while (superclass != null) {
|
||||
result.add(superclass.name)
|
||||
superclass = superclass.superclass
|
||||
}
|
||||
} catch (err: MirroredTypeException) {
|
||||
val typeElement = processingEnv.typeUtils.asElement(err.typeMirror) as TypeElement
|
||||
result.add(typeElement.qualifiedName.toString())
|
||||
|
||||
var superclass = typeElement.superclass
|
||||
while (superclass != null) {
|
||||
val superType = processingEnv.typeUtils.asElement(superclass) as? TypeElement?
|
||||
superType ?: break
|
||||
result.add(superType.qualifiedName.toString())
|
||||
superclass = superType.superclass
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
private fun getViewName(viewAdapter: KSAnnotation): String {
|
||||
return getViewType(viewAdapter).declaration.qualifiedName!!.asString()
|
||||
}
|
||||
|
||||
private fun getViewType(viewAdapter: KSAnnotation) =
|
||||
(getViewAdapterArg(viewAdapter, "forView") as KSType)
|
||||
|
||||
private fun getModuleNs(viewAdapter: KSAnnotation) =
|
||||
(getViewAdapterArg(viewAdapter, "moduleNamespace") as String)
|
||||
|
||||
private fun getViewAdapterArg(viewAdapter: KSAnnotation, arg: String) =
|
||||
viewAdapter.arguments.first { it.name!!.asString() == arg }.value
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.annotations.ksp.inflater
|
||||
|
||||
import com.google.devtools.ksp.containingFile
|
||||
import com.google.devtools.ksp.isOpen
|
||||
import com.google.devtools.ksp.processing.CodeGenerator
|
||||
import com.google.devtools.ksp.processing.Dependencies
|
||||
import com.google.devtools.ksp.processing.KSPLogger
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.google.devtools.ksp.symbol.KSFile
|
||||
import com.itsaky.androidide.annotations.inflater.ViewAdapter
|
||||
|
||||
/**
|
||||
* [SymbolProcessor] for [ViewAdapter] annotations.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ViewAdapterSymbolProcessor(
|
||||
private val codeGenerator: CodeGenerator,
|
||||
private val logger: KSPLogger
|
||||
) : SymbolProcessor {
|
||||
|
||||
private val generator = ViewAdapterIndexGenerator(logger)
|
||||
|
||||
init {
|
||||
generator.init()
|
||||
}
|
||||
|
||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||
val symbols = resolver.getSymbolsWithAnnotation(ViewAdapter::class.java.name)
|
||||
|
||||
if (!symbols.iterator().hasNext()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val unprocessed = mutableListOf<KSAnnotated>()
|
||||
val files = mutableListOf<KSFile>()
|
||||
symbols.forEach {
|
||||
it.containingFile?.also { file -> files.add(file) }
|
||||
if (!process(it)) {
|
||||
unprocessed.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
val file =
|
||||
codeGenerator.createNewFile(
|
||||
Dependencies(true, *files.toTypedArray()),
|
||||
INDEX_PACKAGE_NAME,
|
||||
INDEX_CLASS_NAME,
|
||||
"java"
|
||||
)
|
||||
|
||||
generator.generate(file)
|
||||
|
||||
file.flush()
|
||||
file.close()
|
||||
|
||||
return unprocessed
|
||||
}
|
||||
|
||||
private fun process(sym: KSAnnotated): Boolean {
|
||||
if (sym !is KSClassDeclaration) {
|
||||
logger.error("${ViewAdapter::class} must be applied to a class.", sym)
|
||||
return false
|
||||
}
|
||||
|
||||
if (sym.parent !is KSFile) {
|
||||
logger.error(
|
||||
"${ViewAdapter::class} must be applied to a top level class. (${sym.parent})",
|
||||
sym
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
if (sym.primaryConstructor?.parameters?.size != 0) {
|
||||
logger.error("A view adapter must have a single primary constructor", sym)
|
||||
return false
|
||||
}
|
||||
|
||||
if (sym.typeParameters.size != 1) {
|
||||
logger.error("A view adapter must have exactly one type parameter", sym)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!sym.isOpen()) {
|
||||
logger.error("A view adapter implementation must be open.", sym)
|
||||
return false
|
||||
}
|
||||
|
||||
return generator.addViewAdapter(sym)
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.annotations.ksp.inflater
|
||||
|
||||
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||
import com.google.devtools.ksp.symbol.KSAnnotation
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
fun <T : Annotation> KSAnnotated.getKSAnnotationsByType(annotationKClass: KClass<T>): Sequence<KSAnnotation> {
|
||||
return this.annotations.filter {
|
||||
it.shortName.getShortName() == annotationKClass.simpleName && it.annotationType.resolve().declaration
|
||||
.qualifiedName?.asString() == annotationKClass.qualifiedName
|
||||
}
|
||||
}
|
||||
+6
-23
@@ -15,16 +15,13 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.annotations.processor
|
||||
package com.itsaky.androidide.annotations.ksp.inflater
|
||||
|
||||
import com.squareup.javapoet.ClassName
|
||||
import javax.annotation.processing.ProcessingEnvironment
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.lang.model.type.TypeMirror
|
||||
|
||||
const val KAPT_KOTLIN_GENERATED = "kapt.kotlin.generated"
|
||||
|
||||
const val VIEW_CLASS = "android.view.View"
|
||||
val ANDROID_VIEW_PCK = "android.view"
|
||||
val ANDROID_VIEW_CLASS = "View"
|
||||
val VIEW_CLASS = "$ANDROID_VIEW_PCK.$ANDROID_VIEW_CLASS"
|
||||
|
||||
const val ADAPTER_BASE_CLASS_PCK = "com.itsaky.androidide.inflater"
|
||||
const val ADAPTER_BASE_CLASS_NAME = "IViewAdapter"
|
||||
@@ -39,21 +36,7 @@ const val INDEX_PROVIDER_MAP_FIELD = "widgetProviders"
|
||||
const val METHOD_SET_SUPERCLASS_HIERARCHY = "setSuperclassHierarchy"
|
||||
const val METHOD_SET_MODULE = "setModuleNamespace"
|
||||
|
||||
val viewAdapter = ClassName.get(ADAPTER_BASE_CLASS_PCK, ADAPTER_BASE_CLASS_NAME)
|
||||
val viewAdapterInterface: ClassName =
|
||||
ClassName.get("com.itsaky.androidide.inflater", "IViewAdapterIndex")
|
||||
|
||||
private var adapterElement: TypeElement? = null
|
||||
private var viewElement: TypeElement? = null
|
||||
|
||||
fun ProcessingEnvironment.adapterElement(): TypeElement {
|
||||
return adapterElement
|
||||
?: elementUtils.getTypeElement(ADAPTER_BASE_CLASS)!!.also { adapterElement = it }
|
||||
}
|
||||
|
||||
fun ProcessingEnvironment.viewElement(): TypeElement {
|
||||
return viewElement ?: elementUtils.getTypeElement(VIEW_CLASS)!!.also { viewElement = it }
|
||||
}
|
||||
|
||||
fun ProcessingEnvironment.erasure(typeElement: TypeElement): TypeMirror? {
|
||||
return typeUtils.erasure(typeElement.asType())
|
||||
}
|
||||
val viewClass = ClassName.get(ANDROID_VIEW_PCK, ANDROID_VIEW_CLASS)
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# This file is part of AndroidIDE.
|
||||
#
|
||||
# AndroidIDE is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# AndroidIDE is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
com.itsaky.androidide.annotations.ksp.IDESymbolProcessorProvider
|
||||
-145
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.annotations.processor
|
||||
|
||||
import com.google.auto.service.AutoService
|
||||
import com.itsaky.androidide.annotations.inflater.ViewAdapter
|
||||
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
|
||||
import javax.annotation.processing.AbstractProcessor
|
||||
import javax.annotation.processing.Processor
|
||||
import javax.annotation.processing.RoundEnvironment
|
||||
import javax.lang.model.SourceVersion
|
||||
import javax.lang.model.element.Element
|
||||
import javax.lang.model.element.ElementKind.CLASS
|
||||
import javax.lang.model.element.ElementKind.PACKAGE
|
||||
import javax.lang.model.element.ExecutableElement
|
||||
import javax.lang.model.element.Modifier.FINAL
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.tools.Diagnostic.Kind.ERROR
|
||||
|
||||
@Suppress("unused")
|
||||
@AutoService(Processor::class)
|
||||
class ViewAdapterAnnotationProcessor : AbstractProcessor() {
|
||||
|
||||
private val adapterElement by lazy { processingEnv.erasure(processingEnv.adapterElement()) }
|
||||
private val viewElement by lazy { processingEnv.erasure(processingEnv.viewElement()) }
|
||||
|
||||
private val generator = ViewAdapterIndexGenerator()
|
||||
|
||||
override fun getSupportedAnnotationTypes(): MutableSet<String> {
|
||||
return mutableSetOf(ViewAdapter::class.java.name, IncludeInDesigner::class.java.name)
|
||||
}
|
||||
|
||||
override fun getSupportedSourceVersion(): SourceVersion {
|
||||
return SourceVersion.latestSupported()
|
||||
}
|
||||
|
||||
override fun process(
|
||||
annotations: MutableSet<out TypeElement>?,
|
||||
roundEnv: RoundEnvironment
|
||||
): Boolean {
|
||||
if (roundEnv.processingOver()) {
|
||||
return true
|
||||
}
|
||||
|
||||
generator.init(processingEnv)
|
||||
|
||||
roundEnv.getElementsAnnotatedWith(ViewAdapter::class.java).forEach {
|
||||
if (it.kind != CLASS) {
|
||||
processingEnv.messager.printMessage(
|
||||
ERROR,
|
||||
"@ViewAdapter can only be used with classes. Used on $it"
|
||||
)
|
||||
return true
|
||||
}
|
||||
process(it)
|
||||
}
|
||||
|
||||
generator.generate(
|
||||
processingEnv.options[KAPT_KOTLIN_GENERATED]
|
||||
?: throw IllegalStateException("Cannot find kapt output directory path")
|
||||
)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun process(element: Element) {
|
||||
if (element.enclosingElement?.kind != PACKAGE || element !is TypeElement) {
|
||||
processingEnv.messager.printMessage(
|
||||
ERROR,
|
||||
"@ViewAdapter can only be used on top-level classes. Used on $element"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val constructors =
|
||||
processingEnv.elementUtils.getAllMembers(element).filter {
|
||||
"<init>".contentEquals(it.simpleName)
|
||||
}
|
||||
if (constructors.size != 1) {
|
||||
processingEnv.messager.printMessage(
|
||||
ERROR,
|
||||
"A view adapter must have only default constructor"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if ((constructors.first() as ExecutableElement).parameters.isNotEmpty()) {
|
||||
processingEnv.messager.printMessage(
|
||||
ERROR,
|
||||
"View adapter constructor should not have any parameters"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
!processingEnv.typeUtils.isAssignable(
|
||||
processingEnv.typeUtils.erasure(element.asType()),
|
||||
adapterElement
|
||||
)
|
||||
) {
|
||||
processingEnv.messager.printMessage(
|
||||
ERROR,
|
||||
"Class must be a subtype of $ADAPTER_BASE_CLASS $element $adapterElement"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (element.typeParameters.size != 1) {
|
||||
processingEnv.messager.printMessage(
|
||||
ERROR,
|
||||
"A view adapter must have exactly one type parameter"
|
||||
)
|
||||
}
|
||||
|
||||
val typeParam = element.typeParameters[0]
|
||||
if (!processingEnv.typeUtils.isAssignable(typeParam.asType(), viewElement)) {
|
||||
processingEnv.messager.printMessage(
|
||||
ERROR,
|
||||
"The type parameter of view adapter must be assignable from android.view.View"
|
||||
)
|
||||
}
|
||||
|
||||
if (element.modifiers.contains(FINAL)) {
|
||||
processingEnv.messager.printMessage(ERROR, "${element.qualifiedName} cannot be final")
|
||||
return
|
||||
}
|
||||
|
||||
generator.addViewAdapter(processingEnv, element)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -6,6 +6,7 @@ editor = "0.20.4"
|
||||
glide = "4.14.2"
|
||||
androidx-vectordrawable = "1.1.0"
|
||||
androidx-navigation = "2.5.3"
|
||||
ksp = "1.8.0-1.0.8"
|
||||
|
||||
[libraries]
|
||||
|
||||
@@ -30,6 +31,7 @@ common-kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.re
|
||||
common-jkotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
|
||||
common-eventbus_ap = { module = "org.greenrobot:eventbus-annotation-processor", version = "3.3.1" }
|
||||
common-javapoet = { module = "com.squareup:javapoet", version = "1.13.0" }
|
||||
common-ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
||||
|
||||
# AndroidX
|
||||
androidx-annotation = { module = "androidx.annotation:annotation", version = "1.5.0" }
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ dependencyResolutionManagement {
|
||||
rootProject.name = "AndroidIDE"
|
||||
|
||||
include(
|
||||
":annotation-processors",
|
||||
":annotation-ksp",
|
||||
":annotations",
|
||||
":actions",
|
||||
":app",
|
||||
|
||||
@@ -20,7 +20,7 @@ dependencies {
|
||||
implementation(libs.common.kotlin)
|
||||
implementation(libs.common.utilcode)
|
||||
implementation(libs.google.material)
|
||||
|
||||
|
||||
implementation(projects.actions)
|
||||
implementation(projects.annotations)
|
||||
implementation(projects.common)
|
||||
@@ -31,7 +31,7 @@ dependencies {
|
||||
implementation(projects.lsp.xml)
|
||||
implementation(projects.resources)
|
||||
implementation(projects.xmlInflater)
|
||||
|
||||
|
||||
testImplementation(libs.tests.junit)
|
||||
testImplementation(libs.tests.google.truth)
|
||||
testImplementation(libs.tests.robolectric)
|
||||
|
||||
@@ -33,7 +33,6 @@ import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem.Location.UI_DESIGNER_TOOLBAR
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import com.itsaky.androidide.app.BaseIDEActivity
|
||||
import com.itsaky.androidide.inflater.IView
|
||||
import com.itsaky.androidide.uidesigner.actions.clearUiDesignerActions
|
||||
import com.itsaky.androidide.uidesigner.actions.registerUiDesignerActions
|
||||
import com.itsaky.androidide.uidesigner.databinding.ActivityUiDesignerBinding
|
||||
@@ -53,7 +52,7 @@ class UIDesignerActivity : BaseIDEActivity() {
|
||||
private var binding: ActivityUiDesignerBinding? = null
|
||||
private val log = ILogger.newInstance("UIDesignerActivity")
|
||||
private val viewModel by viewModels<WorkspaceViewModel>()
|
||||
|
||||
|
||||
private val workspace: DesignerWorkspaceFragment?
|
||||
get() = this.binding?.workspace?.getFragment<DesignerWorkspaceFragment>()
|
||||
|
||||
@@ -183,9 +182,7 @@ class UIDesignerActivity : BaseIDEActivity() {
|
||||
}
|
||||
|
||||
fun setupHierarchy(view: com.itsaky.androidide.inflater.IView) {
|
||||
binding?.hierarchy?.setupWithView(view) {
|
||||
workspace()?.showViewInfo(it)
|
||||
}
|
||||
binding?.hierarchy?.setupWithView(view) { workspace()?.showViewInfo(it) }
|
||||
}
|
||||
|
||||
fun openHierarchyView() {
|
||||
|
||||
@@ -46,8 +46,8 @@ internal object Widgets {
|
||||
}
|
||||
this.widgets =
|
||||
IViewAdapterIndex.instance.getWidgetProviders(WIDGETS)
|
||||
.flatMap { it.getUiWidgets() }
|
||||
.sortedBy { it.name.simpleName() }
|
||||
?.flatMap { it.getUiWidgets() }
|
||||
?.sortedBy { it.name.simpleName() } ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ internal object Widgets {
|
||||
|
||||
this.widgets =
|
||||
IViewAdapterIndex.instance.getWidgetProviders(LAYOUTS)
|
||||
.flatMap { it.getUiWidgets() }
|
||||
.sortedBy { it.name.simpleName() }
|
||||
?.flatMap { it.getUiWidgets() }
|
||||
?.sortedBy { it.name.simpleName() } ?: emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
id("kotlin-kapt")
|
||||
id("kotlin-parcelize")
|
||||
id("com.google.devtools.ksp") version libs.versions.ksp
|
||||
}
|
||||
|
||||
android { namespace = "com.itsaky.androidide.inflater" }
|
||||
|
||||
dependencies {
|
||||
|
||||
kapt(projects.annotationProcessors)
|
||||
ksp(projects.annotationKsp)
|
||||
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.common.kotlin)
|
||||
|
||||
@@ -30,15 +30,6 @@ import com.itsaky.androidide.inflater.models.UiWidget
|
||||
*/
|
||||
abstract class IViewAdapter<T : View> : AbstractParser() {
|
||||
|
||||
/** Superclasses of the view that this adpater handles. */
|
||||
var superclassHierarchy: List<String> = emptyList()
|
||||
set(value) {
|
||||
if (field.isNotEmpty()) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
/**
|
||||
* The package name or namespace of the module/artifact in which the view that this adapter
|
||||
* handles is defined. The value is set to "android" by default unless explicitly specified in the
|
||||
|
||||
@@ -39,7 +39,7 @@ interface IViewAdapterIndex {
|
||||
* @return The [IViewAdapter] for the given view name. If no adapter is found for the given view
|
||||
* name, returns adapter for `android.view.View` if available or `null`.
|
||||
*/
|
||||
fun getAdapter(view: String): IViewAdapter<*>?
|
||||
fun getAdapter(view: String): IViewAdapter<out View>?
|
||||
|
||||
/**
|
||||
* Get the adapter for the given view.
|
||||
@@ -47,15 +47,15 @@ interface IViewAdapterIndex {
|
||||
* @param view The fully qualified name of the view.
|
||||
* @return The view adapter instance or `null` if not found.
|
||||
*/
|
||||
fun getViewAdapter(view: String): IViewAdapter<*>
|
||||
|
||||
fun getViewAdapter(view: String): IViewAdapter<out View>?
|
||||
|
||||
/**
|
||||
* Get the view adapters which provide widgets to the UI designer.
|
||||
*
|
||||
* @param group The group to get widget providers for.
|
||||
* @return The widget providers for the [group].
|
||||
*/
|
||||
fun getWidgetProviders(group: IncludeInDesigner.Group) : List<IViewAdapter<*>>
|
||||
fun getWidgetProviders(group: IncludeInDesigner.Group): List<IViewAdapter<out View>>?
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +65,7 @@ interface IViewAdapterIndex {
|
||||
* @return The [IViewAdapter] for the given view name. If no adapter is found for the given view
|
||||
* name, returns adapter for `android.view.View` if available or `null`.
|
||||
*/
|
||||
fun getAdapter(view: String): IViewAdapter<*>? {
|
||||
fun getAdapter(view: String): IViewAdapter<out View>? {
|
||||
return IViewAdapterIndex.instance.getAdapter(view)
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ fun getAdapter(view: String): IViewAdapter<*>? {
|
||||
* @param view The fully qualified name of the view.
|
||||
* @return The view adapter instance or `null` if not found.
|
||||
*/
|
||||
fun getViewAdapter(view: String): IViewAdapter<*> {
|
||||
fun getViewAdapter(view: String): IViewAdapter<out View>? {
|
||||
return IViewAdapterIndex.instance.getViewAdapter(view)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user