Create descriptors directly in Android Extensions (compiler plugin)

This commit is contained in:
Yan Zhulanow
2015-11-06 18:34:58 +03:00
parent 3d8df88ab2
commit d88c2249b8
26 changed files with 757 additions and 660 deletions
@@ -17,10 +17,7 @@
package org.jetbrains.kotlin.android.synthetic
import com.intellij.mock.MockProject
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenExtension
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidOnDestroyClassBuilderInterceptorExtension
import org.jetbrains.kotlin.android.synthetic.diagnostic.AndroidExtensionPropertiesCallChecker
@@ -37,10 +34,9 @@ import org.jetbrains.kotlin.config.CompilerConfigurationKey
import org.jetbrains.kotlin.container.StorageComponentContainer
import org.jetbrains.kotlin.container.useInstance
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
public object AndroidConfigurationKeys {
@@ -73,13 +69,6 @@ public class AndroidCommandLineProcessor : CommandLineProcessor {
}
}
public class CliAndroidDeclarationsProvider(private val project: Project) : ExternalDeclarationsProvider {
override fun getExternalDeclarations(moduleInfo: ModuleInfo?): Collection<KtFile> {
val parser = ServiceManager.getService(project, SyntheticFileGenerator::class.java) as? CliSyntheticFileGenerator
return parser?.getSyntheticFiles() ?: listOf()
}
}
public class AndroidComponentRegistrar : ComponentRegistrar {
public override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
@@ -87,18 +76,14 @@ public class AndroidComponentRegistrar : ComponentRegistrar {
val variants = configuration.get(AndroidConfigurationKeys.VARIANT)?.map { parseVariant(it) }?.filterNotNull() ?: emptyList()
if (variants.isNotEmpty() && !applicationPackage.isNullOrBlank()) {
val xmlProcessor = CliSyntheticFileGenerator(project, applicationPackage!!, variants)
project.registerService(SyntheticFileGenerator::class.java, xmlProcessor)
val layoutXmlFileManager = CliAndroidLayoutXmlFileManager(project, applicationPackage, variants)
val layoutXmlFileManager = CliAndroidLayoutXmlFileManager(project, applicationPackage!!, variants)
project.registerService(AndroidLayoutXmlFileManager::class.java, layoutXmlFileManager)
ExternalDeclarationsProvider.registerExtension(project, CliAndroidDeclarationsProvider(project))
ExpressionCodegenExtension.registerExtension(project, AndroidExpressionCodegenExtension())
StorageComponentContainerContributor.registerExtension(project, AndroidExtensionPropertiesComponentContainerContributor())
Extensions.getRootArea().getExtensionPoint(DefaultErrorMessages.Extension.EP_NAME).registerExtension(DefaultErrorMessagesAndroid())
ClassBuilderInterceptorExtension.registerExtension(project, AndroidOnDestroyClassBuilderInterceptorExtension())
PackageFragmentProviderExtension.registerExtension(project, CliAndroidPackageFragmentProviderExtension())
}
}
@@ -16,20 +16,19 @@
package org.jetbrains.kotlin.android.synthetic
import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens
public object AndroidConst {
val ANDROID_USER_PACKAGE: Key<String> = Key.create<String>("ANDROID_USER_PACKAGE")
val SYNTHETIC_FILENAME_PREFIX: String = "ANDROIDXML_"
val LAYOUT_POSTFIX: String = "_LAYOUT"
val VIEW_LAYOUT_POSTFIX: String = "_VIEW"
val SYNTHETIC_PACKAGE: String = "kotlinx.android.synthetic"
val SYNTHETIC_PACKAGE_PATH_LENGTH = SYNTHETIC_PACKAGE.count { it == '.' } + 1
val SYNTHETIC_SUBPACKAGES: List<String> = SYNTHETIC_PACKAGE.split('.').fold(arrayListOf<String>()) { list, segment ->
val prevSegment = list.lastOrNull()?.let { "$it." } ?: ""
list += "$prevSegment$segment"
list
}
val ANDROID_NAMESPACE: String = "android"
val ID_ATTRIBUTE_NO_NAMESPACE: String = "id"
val ID_ATTRIBUTE: String = "$ANDROID_NAMESPACE:$ID_ATTRIBUTE_NO_NAMESPACE"
@@ -41,6 +40,9 @@ public object AndroidConst {
val CLEAR_FUNCTION_NAME = "clearFindViewByIdCache"
//TODO FqName / ClassId
val VIEW_FQNAME = "android.view.View"
val ACTIVITY_FQNAME = "android.app.Activity"
val FRAGMENT_FQNAME = "android.app.Fragment"
@@ -56,8 +58,6 @@ public object AndroidConst {
val FQNAME_RESOLVE_PACKAGES = listOf("android.widget", "android.webkit", "android.view")
}
public fun nameToIdDeclaration(name: String): String = AndroidConst.ID_DECLARATION_PREFIX + name
public fun idToName(id: String): String? {
for (prefix in AndroidConst.XML_ID_PREFIXES) {
if (id.startsWith(prefix)) return escapeAndroidIdentifier(id.replace(prefix, ""))
@@ -71,4 +71,13 @@ public fun isWidgetTypeIgnored(xmlType: String): Boolean {
fun escapeAndroidIdentifier(id: String): String {
return if (id in AndroidConst.ESCAPED_IDENTIFIERS) "`$id`" else id
}
}
internal fun <T> List<T>.forEachUntilLast(operation: (T) -> Unit) {
val lastIndex = lastIndex
forEachIndexed { i, t ->
if (i < lastIndex) {
operation(t)
}
}
}
@@ -1,85 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.android.synthetic
import java.util.ArrayList
open class Context(val buffer: StringBuffer = StringBuffer(), private var indentDepth: Int = 0) {
open class InvalidIndent(num: Int) : RuntimeException("Indentation level < 0: $num")
val indentUnit = " "
protected var currentIndent: String = indentUnit.repeat(indentDepth)
private val children = ArrayList<Context>()
public fun incIndent() {
indentDepth++
currentIndent += indentUnit
}
public fun decIndent() {
indentDepth--
if (indentDepth < 0)
throw InvalidIndent(indentDepth)
currentIndent = currentIndent.substring(0, currentIndent.length - indentUnit.length)
}
public open fun write(what: String) {
writeNoIndent(currentIndent)
writeNoIndent(what)
}
public fun writeNoIndent(what: String) {
buffer.append(what)
}
public fun writeln(what: String) {
write(what)
newLine()
}
public fun newLine() {
writeNoIndent("\n")
}
public fun trim(num: Int) {
buffer.delete(buffer.length - num, buffer.length())
}
public fun fork(newBuffer: StringBuffer = StringBuffer(),
newIndentDepth: Int = indentDepth): Context {
val child = Context(newBuffer, newIndentDepth)
children.add(child)
return child
}
public fun absorbChildren(noIndent: Boolean = true) {
for (child in children) {
child.absorbChildren()
if (noIndent)
writeNoIndent(child.toString())
else
write(child.toString())
}
children.clear()
}
public override fun toString(): String {
return buffer.toString()
}
}
@@ -1,85 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.android.synthetic
interface KotlinWriter {
fun toStringBuffer(): StringBuffer
}
class KotlinStringWriter : KotlinWriter {
private val ctx = Context()
private val imports = ctx.fork()
private val body = ctx.fork()
fun writeImmutableProperty(name: String,
retType: String,
getterBody: Collection<String>) {
body.writeln("val $name: $retType")
body.incIndent()
body.write("get() ")
if (getterBody.size > 1) {
body.writeNoIndent("{\n")
body.incIndent()
for (stmt in getterBody) {
body.writeln(stmt)
}
body.decIndent()
body.writeln("}")
}
else {
body.writeNoIndent("=")
body.writeNoIndent(getterBody.joinToString("").replace("return", ""))
body.newLine()
}
body.decIndent()
body.newLine()
}
fun writeImmutableExtensionProperty(receiver: String,
name: String,
retType: String,
getterBody: Collection<String>) {
writeImmutableProperty("$receiver.$name", retType, getterBody)
}
fun writeImport(what: String) {
imports.writeln("import $what")
}
fun writePackage(_package: String) {
ctx.writeln("package $_package\n")
}
fun writeEmptyLine() {
body.newLine()
}
fun writeText(text: String) {
body.writeln(text)
}
override fun toStringBuffer(): StringBuffer {
ctx.absorbChildren()
return ctx.buffer
}
override fun toString(): String {
return ctx.buffer.toString()
}
}
@@ -17,6 +17,9 @@
package org.jetbrains.kotlin.android.synthetic.codegen
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.descriptors.AndroidSyntheticPackageFragmentDescriptor
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticFunction
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticProperty
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.FunctionCodegen
import org.jetbrains.kotlin.codegen.StackValue
@@ -26,7 +29,6 @@ import org.jetbrains.kotlin.codegen.state.JetTypeMapper
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
@@ -109,12 +111,12 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
else null
}
override fun applyFunction(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): Boolean {
override fun applyFunction(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
val resultingDescriptor = resolvedCall.resultingDescriptor
return if (resultingDescriptor is FunctionDescriptor) {
return generateSyntheticFunctionCall(receiver, resolvedCall, c, resultingDescriptor)
}
else false
else null
}
private fun generateSyntheticFunctionCall(
@@ -122,22 +124,22 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
resolvedCall: ResolvedCall<*>,
c: ExpressionCodegenExtension.Context,
descriptor: FunctionDescriptor
): Boolean {
if (descriptor.getAndroidPackage() == null) return false
if (descriptor.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME) return false
): StackValue? {
if (descriptor !is AndroidSyntheticFunction) return null
if (descriptor.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME) return null
val declarationDescriptor = resolvedCall.getReceiverDeclarationDescriptor() ?: return false
if (!isCacheSupported(declarationDescriptor)) return true
val declarationDescriptor = resolvedCall.getReceiverDeclarationDescriptor() ?: return null
if (!isCacheSupported(declarationDescriptor)) return StackValue.functionCall(Type.VOID_TYPE) {}
val androidClassType = AndroidClassType.getClassType(declarationDescriptor)
if (androidClassType == AndroidClassType.UNKNOWN) return false
if (androidClassType == AndroidClassType.UNKNOWN) return null
val bytecodeClassName = c.typeMapper.mapType(declarationDescriptor).internalName
return StackValue.functionCall(Type.VOID_TYPE) {
val bytecodeClassName = c.typeMapper.mapType(declarationDescriptor).internalName
receiver.put(c.typeMapper.mapType(declarationDescriptor), c.v)
c.v.invokevirtual(bytecodeClassName, CLEAR_CACHE_METHOD_NAME, "()V", false)
return true
receiver.put(c.typeMapper.mapType(declarationDescriptor), it)
it.invokevirtual(bytecodeClassName, CLEAR_CACHE_METHOD_NAME, "()V", false)
}
}
private fun generateSyntheticPropertyCall(
@@ -146,7 +148,9 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
c: ExpressionCodegenExtension.Context,
descriptor: PropertyDescriptor
): StackValue? {
val androidPackage = descriptor.getAndroidPackage() ?: return null
if (descriptor !is AndroidSyntheticProperty) return null
val packageFragment = descriptor.containingDeclaration as? AndroidSyntheticPackageFragmentDescriptor ?: return null
val androidPackage = packageFragment.packageData.moduleData.module.applicationPackage
val declarationDescriptor = resolvedCall.getReceiverDeclarationDescriptor() ?: return null
val androidClassType = AndroidClassType.getClassType(declarationDescriptor)
@@ -224,10 +228,6 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
}
}
private fun CallableDescriptor.getAndroidPackage(): String? {
return DescriptorToSourceUtils.getContainingFile(this)?.getUserData(AndroidConst.ANDROID_USER_PACKAGE)
}
private fun ResolvedCall<*>.getReceiverDeclarationDescriptor(): ClassifierDescriptor? {
return (extensionReceiver as ReceiverValue).type.constructor.declarationDescriptor
}
@@ -0,0 +1,87 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.android.synthetic.descriptors
import org.jetbrains.kotlin.android.synthetic.res.*
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.utils.Printer
import java.util.*
class AndroidSyntheticPackageData(
val moduleData: AndroidModuleData,
val forView: Boolean,
val isDeprecated: Boolean,
val resources: List<AndroidResource>)
class AndroidSyntheticPackageFragmentDescriptor(
module: ModuleDescriptor,
fqName: FqName,
val packageData: AndroidSyntheticPackageData,
private val lazyContext: LazySyntheticElementResolveContext,
private val storageManager: StorageManager
) : PackageFragmentDescriptorImpl(module, fqName) {
private val scope = AndroidExtensionPropertiesScope()
override fun getMemberScope(): MemberScope = scope
private inner class AndroidExtensionPropertiesScope : MemberScopeImpl() {
private val properties = storageManager.createLazyValue {
val packageFragmentDescriptor = this@AndroidSyntheticPackageFragmentDescriptor
val context = lazyContext()
val widgetReceivers = context.getWidgetReceivers(packageData.forView)
val fragmentTypes = context.fragmentTypes
val properties = ArrayList<PropertyDescriptor>(0)
for (resource in packageData.resources) {
when (resource) {
is AndroidResource.Widget -> {
val resolvedWidget = resource.resolve(module)
for (receiver in widgetReceivers) {
properties += genPropertyForWidget(packageFragmentDescriptor, receiver, resolvedWidget, context)
}
}
is AndroidResource.Fragment -> if (!packageData.forView) {
for ((receiverType, type) in fragmentTypes) {
properties += genPropertyForFragment(packageFragmentDescriptor, receiverType, type, resource)
}
}
}
}
properties
}
override fun getContributedDescriptors(kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean) =
properties().filter { kindFilter.acceptsKinds(DescriptorKindFilter.VARIABLES_MASK) && nameFilter(it.name) }
override fun getContributedVariables(name: Name, location: LookupLocation) = properties().filter { it.name == name }
override fun printScopeStructure(p: Printer) {
p.println(javaClass.simpleName)
}
}
}
@@ -0,0 +1,94 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.android.synthetic.descriptors
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType
import java.util.*
class LazySyntheticElementResolveContext(private val module: ModuleDescriptor, private val storageManager: StorageManager) {
private val context = storageManager.createLazyValue {
module.createResolveContext()
}
internal operator fun invoke() = context()
private fun ModuleDescriptor.createResolveContext(): SyntheticElementResolveContext {
fun find(fqName: String) = module.findClassAcrossModuleDependencies(ClassId.topLevel(FqName(fqName)))
val viewDescriptor = find(AndroidConst.VIEW_FQNAME) ?: return SyntheticElementResolveContext.ERROR_CONTEXT
val activityDescriptor = find(AndroidConst.ACTIVITY_FQNAME) ?: return SyntheticElementResolveContext.ERROR_CONTEXT
val fragmentDescriptor = find(AndroidConst.FRAGMENT_FQNAME)
val supportActivityDescriptor = find(AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME)
val supportFragmentDescriptor = find(AndroidConst.SUPPORT_FRAGMENT_FQNAME)
return SyntheticElementResolveContext(
viewDescriptor.defaultType,
activityDescriptor.defaultType,
fragmentDescriptor?.defaultType,
supportActivityDescriptor?.defaultType,
supportFragmentDescriptor?.defaultType)
}
}
internal class SyntheticElementResolveContext(
val viewType: KotlinType,
val activityType: KotlinType,
val fragmentType: KotlinType?,
val supportActivityType: KotlinType?,
val supportFragmentType: KotlinType?) {
companion object {
private fun errorType() = ErrorUtils.createErrorType("")
val ERROR_CONTEXT = SyntheticElementResolveContext(errorType(), errorType(), null, null, null)
}
private val widgetReceivers by lazy {
val receivers = ArrayList<KotlinType>(3)
receivers += activityType
fragmentType?.let { receivers += it }
supportFragmentType?.let { receivers += it }
receivers
}
val fragmentTypes: List<Pair<KotlinType, KotlinType>> by lazy {
if (fragmentType == null) {
emptyList<Pair<KotlinType, KotlinType>>()
}
else {
val types = ArrayList<Pair<KotlinType, KotlinType>>(4)
types += Pair(activityType, fragmentType)
types += Pair(fragmentType, fragmentType)
if (supportActivityType != null && supportFragmentType != null) {
types += Pair(supportFragmentType, supportFragmentType)
types += Pair(supportActivityType, supportFragmentType)
}
types
}
}
fun getWidgetReceivers(forView: Boolean): List<KotlinType> {
if (forView) return listOf(viewType)
return widgetReceivers
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.android.synthetic.descriptors
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.utils.Printer
class PredefinedPackageFragmentDescriptor(
fqName: String,
module: ModuleDescriptor,
private val storageManager: StorageManager,
private val descriptors: (PredefinedPackageFragmentDescriptor) -> Collection<DeclarationDescriptor> = { emptyList() }
) : PackageFragmentDescriptorImpl(module, FqName(fqName)) {
private val calculatedDescriptors = storageManager.createLazyValue {
descriptors(this)
}
private val scope = PredefinedScope()
override fun getMemberScope() = scope
inner class PredefinedScope : MemberScopeImpl() {
@Suppress("UNCHECKED_CAST")
override fun getContributedVariables(name: Name, location: LookupLocation) =
calculatedDescriptors().filter { it is PropertyDescriptor && it.name == name } as List<PropertyDescriptor>
@Suppress("UNCHECKED_CAST")
override fun getContributedFunctions(name: Name, location: LookupLocation) =
calculatedDescriptors().filter { it is FunctionDescriptor && it.name == name } as List<FunctionDescriptor>
override fun getContributedDescriptors(kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean) =
calculatedDescriptors().filter { nameFilter(it.name) && kindFilter.accepts(it) }
override fun printScopeStructure(p: Printer) {
p.println(javaClass.simpleName)
}
}
}
@@ -16,32 +16,42 @@
package org.jetbrains.kotlin.android.synthetic.diagnostic
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.android.synthetic.diagnostic.ErrorsAndroid.*
import org.jetbrains.kotlin.android.synthetic.descriptors.AndroidSyntheticPackageFragmentDescriptor
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticProperty
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.psi.KtExpression
public class AndroidExtensionPropertiesCallChecker : CallChecker {
override fun <F : CallableDescriptor> check(resolvedCall: ResolvedCall<F>, context: BasicCallResolutionContext) {
val expression = context.call.calleeExpression ?: return
val propertyDescriptor = resolvedCall.resultingDescriptor as? PropertyDescriptor ?: return
val syntheticPackage = propertyDescriptor.containingDeclaration as? PackageFragmentDescriptor ?: return
if (!syntheticPackage.fqName.asString().startsWith("${AndroidConst.SYNTHETIC_PACKAGE}.")) return
val containingPackage = propertyDescriptor.containingDeclaration as? AndroidSyntheticPackageFragmentDescriptor ?: return
val androidSyntheticProperty = propertyDescriptor as? AndroidSyntheticProperty ?: return
val invalidWidgetTypeAnnotation = propertyDescriptor.annotations.findAnnotation(
SyntheticFileGenerator.INVALID_WIDGET_TYPE_ANNOTATION_FQNAME) ?: return
val type = invalidWidgetTypeAnnotation.allValueArguments.filterKeys {
it.name.asString() == SyntheticFileGenerator.INVALID_WIDGET_TYPE_ANNOTATION_TYPE_PARAMETER
}.values().firstOrNull() as? StringValue ?: return
val erroneousType = type.value
val warning = if (erroneousType.contains('.')) SYNTHETIC_UNRESOLVED_WIDGET_TYPE else SYNTHETIC_INVALID_WIDGET_TYPE
context.trace.report(warning.on(expression, erroneousType))
with (context.trace) {
checkUnresolvedWidgetType(expression, androidSyntheticProperty)
checkDeprecated(expression, containingPackage)
}
}
private fun DiagnosticSink.checkDeprecated(expression: KtExpression, packageDescriptor: AndroidSyntheticPackageFragmentDescriptor) {
if (packageDescriptor.packageData.isDeprecated) {
report(SYNTHETIC_DEPRECATED_PACKAGE.on(expression))
}
}
private fun DiagnosticSink.checkUnresolvedWidgetType(expression: KtExpression, property: AndroidSyntheticProperty) {
if (!property.isErrorType) return
val type = property.errorType ?: return
val warning = if (type.contains('.')) SYNTHETIC_UNRESOLVED_WIDGET_TYPE else SYNTHETIC_INVALID_WIDGET_TYPE
report(warning.on(expression, type))
}
}
@@ -33,6 +33,9 @@ public class DefaultErrorMessagesAndroid : DefaultErrorMessages.Extension {
MAP.put(ErrorsAndroid.SYNTHETIC_UNRESOLVED_WIDGET_TYPE,
"Widget has an unresolved type ''{0}'', and thus it was upcasted to ''android.view.View''",
Renderers.TO_STRING)
MAP.put(ErrorsAndroid.SYNTHETIC_DEPRECATED_PACKAGE,
"Use properties from the build variant packages")
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.android.synthetic.diagnostic;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.psi.KtExpression;
@@ -25,6 +26,7 @@ import static org.jetbrains.kotlin.diagnostics.Severity.WARNING;
public interface ErrorsAndroid {
DiagnosticFactory1<KtExpression, String> SYNTHETIC_INVALID_WIDGET_TYPE = DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<KtExpression, String> SYNTHETIC_UNRESOLVED_WIDGET_TYPE = DiagnosticFactory1.create(WARNING);
DiagnosticFactory0<KtExpression> SYNTHETIC_DEPRECATED_PACKAGE = DiagnosticFactory0.create(WARNING);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
@@ -24,6 +24,9 @@ import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.psi.KtProperty
import java.util.*
@@ -38,9 +41,9 @@ abstract class AndroidLayoutXmlFileManager(val project: Project) {
public abstract val androidModule: AndroidModule?
public open fun propertyToXmlAttributes(property: KtProperty): List<PsiElement> = listOf()
public open fun propertyToXmlAttributes(propertyDescriptor: PropertyDescriptor): List<PsiElement> = listOf()
public fun getLayoutXmlFiles(): AndroidModuleData {
open fun getModuleData(): AndroidModuleData {
val androidModule = androidModule ?: return AndroidModuleData.EMPTY
return AndroidModuleData(androidModule, androidModule.variants.map { getVariantData(it) })
}
@@ -82,6 +85,45 @@ abstract class AndroidLayoutXmlFileManager(val project: Project) {
return AndroidVariantData(variant, layoutNameToXmlFiles)
}
fun extractResources(files: List<PsiFile>, module: ModuleDescriptor): List<AndroidResource> {
return filterDuplicates(doExtractResources(files, module))
}
protected abstract fun doExtractResources(files: List<PsiFile>, module: ModuleDescriptor): List<AndroidResource>
protected fun parseAndroidResource(id: String, tag: String, sourceElement: PsiElement?): AndroidResource {
return when (tag) {
"fragment" -> AndroidResource.Fragment(id, sourceElement)
"include" -> AndroidResource.Widget(id, AndroidConst.VIEW_FQNAME, sourceElement)
else -> AndroidResource.Widget(id, tag, sourceElement)
}
}
private fun filterDuplicates(resources: List<AndroidResource>): List<AndroidResource> {
val resourceMap = linkedMapOf<String, AndroidResource>()
val resourcesToExclude = hashSetOf<String>()
for (res in resources) {
if (resourceMap.contains(res.id)) {
val existing = resourceMap[res.id]!!
if (!res.sameClass(existing)) {
resourcesToExclude.add(res.id)
}
else if (res is AndroidResource.Widget && existing is AndroidResource.Widget) {
// Widgets with the same id but different types exist.
if (res.xmlType != existing.xmlType && existing.xmlType != AndroidConst.VIEW_FQNAME) {
resourceMap.put(res.id, AndroidResource.Widget(res.id, AndroidConst.VIEW_FQNAME, res.sourceElement))
}
}
}
else resourceMap.put(res.id, res)
}
resourcesToExclude.forEach { resourceMap.remove(it) }
return resourceMap.values.toList()
}
companion object {
public fun getInstance(module: Module): AndroidLayoutXmlFileManager? {
val service = ModuleServiceManager.getService(module, AndroidLayoutXmlFileManager::class.java)
@@ -0,0 +1,106 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.descriptors.*
import org.jetbrains.kotlin.android.synthetic.forEachUntilLast
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
import org.jetbrains.kotlin.storage.StorageManager
abstract class AndroidPackageFragmentProviderExtension : PackageFragmentProviderExtension {
protected abstract fun getLayoutXmlFileManager(project: Project, moduleInfo: ModuleInfo?): AndroidLayoutXmlFileManager?
private val cache = WeakHashMap<AndroidModuleData, AndroidSyntheticPackageFragmentProvider>()
override fun getPackageFragmentProvider(
project: Project,
module: ModuleDescriptor,
storageManager: StorageManager,
trace: BindingTrace,
moduleInfo: ModuleInfo?
): PackageFragmentProvider? {
val layoutXmlFileManager = getLayoutXmlFileManager(project, moduleInfo) ?: return null
val moduleData = layoutXmlFileManager.getModuleData()
val cachedPackageFragmentProvider = cache[moduleData]
if (cachedPackageFragmentProvider != null) return cachedPackageFragmentProvider
val lazyContext = LazySyntheticElementResolveContext(module, storageManager)
val packageDescriptors = arrayListOf<PackageFragmentDescriptor>()
// Packages with synthetic properties
for (variantData in moduleData) {
for ((layoutName, layouts) in variantData) {
fun createPackageFragment(fqName: String, forView: Boolean, isDeprecated: Boolean = false): PackageFragmentDescriptor {
val resources = layoutXmlFileManager.extractResources(layouts, module)
val packageData = AndroidSyntheticPackageData(moduleData, forView, isDeprecated, resources)
return AndroidSyntheticPackageFragmentDescriptor(module, FqName(fqName), packageData, lazyContext, storageManager)
}
val packageFqName = AndroidConst.SYNTHETIC_PACKAGE + '.' + variantData.variant.name + '.' + layoutName
packageDescriptors += createPackageFragment(packageFqName, false)
packageDescriptors += createPackageFragment(packageFqName + ".view", true)
if (variantData.variant.isMainVariant && !moduleData.any { it.variant.name == layoutName }) {
val deprecatedPackageFqName = AndroidConst.SYNTHETIC_PACKAGE + '.' + layoutName
packageDescriptors += createPackageFragment(deprecatedPackageFqName, false, isDeprecated = true)
packageDescriptors += createPackageFragment(deprecatedPackageFqName + ".view", true, isDeprecated = true)
}
}
}
// Empty middle packages
AndroidConst.SYNTHETIC_SUBPACKAGES.forEachUntilLast { s ->
packageDescriptors += PredefinedPackageFragmentDescriptor(s, module, storageManager)
}
for (variantData in moduleData) {
val fqName = AndroidConst.SYNTHETIC_PACKAGE + '.' + variantData.variant.name
packageDescriptors += PredefinedPackageFragmentDescriptor(fqName, module, storageManager)
}
// Package with clearFindViewByIdCache()
AndroidConst.SYNTHETIC_SUBPACKAGES.last().let { s ->
packageDescriptors += PredefinedPackageFragmentDescriptor(s, module, storageManager) { descriptor ->
lazyContext().getWidgetReceivers(false).map { genClearCacheFunction(descriptor, it) }
}
}
val provider = AndroidSyntheticPackageFragmentProvider(allPackageDescriptors)
cache[moduleData] = provider
return provider
}
}
class AndroidSyntheticPackageFragmentProvider(val packageFragments: Collection<PackageFragmentDescriptor>) : PackageFragmentProvider {
override fun getPackageFragments(fqName: FqName) = packageFragments.filter { it.fqName == fqName }
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean) =
packageFragments.asSequence()
.map { it.fqName }
.filter { !it.isRoot && it.parent() == fqName }
.toList()
}
@@ -16,7 +16,12 @@
package org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.android.synthetic.AndroidXmlHandler
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import java.io.ByteArrayInputStream
import javax.xml.parsers.SAXParser
import javax.xml.parsers.SAXParserFactory
@@ -25,10 +30,32 @@ public class CliAndroidLayoutXmlFileManager(
private val applicationPackage: String,
private val variants: List<AndroidVariant>
) : AndroidLayoutXmlFileManager(project) {
private companion object {
val LOG = Logger.getInstance(CliAndroidLayoutXmlFileManager::class.java)
}
override val androidModule by lazy { AndroidModule(applicationPackage, variants) }
override val androidModule = AndroidModule(applicationPackage, variants)
val saxParser: SAXParser = initSAX()
private val saxParser: SAXParser = initSAX()
override fun doExtractResources(files: List<PsiFile>, module: ModuleDescriptor): List<AndroidResource> {
val resources = arrayListOf<AndroidResource>()
val handler = AndroidXmlHandler { id, tag ->
resources += parseAndroidResource(id, tag, null)
}
for (file in files) {
try {
val inputStream = ByteArrayInputStream(file.virtualFile.contentsToByteArray())
saxParser.parse(inputStream, handler)
} catch (e: Throwable) {
LOG.error(e)
}
}
return resources
}
protected fun initSAX(): SAXParser {
val saxFactory = SAXParserFactory.newInstance()
@@ -14,19 +14,14 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.android.synthetic
package org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Computable
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.ModuleInfo
fun isAndroidSyntheticFile(f: PsiFile?): Boolean {
return f?.getUserData(AndroidConst.ANDROID_USER_PACKAGE) != null
}
public fun isAndroidSyntheticElement(element: PsiElement?): Boolean {
return isAndroidSyntheticFile(ApplicationManager.getApplication().runReadAction(Computable {
element?.containingFile
}))
}
class CliAndroidPackageFragmentProviderExtension : AndroidPackageFragmentProviderExtension() {
override fun getLayoutXmlFileManager(project: Project, moduleInfo: ModuleInfo?): AndroidLayoutXmlFileManager? {
return ServiceManager.getService(project, AndroidLayoutXmlFileManager::class.java)
}
}
@@ -1,91 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementFinder
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.PsiElementFinderImpl
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.AndroidXmlHandler
import org.jetbrains.kotlin.psi.KtFile
import java.io.ByteArrayInputStream
public open class CliSyntheticFileGenerator(
project: Project,
private val manifestPath: String,
private val variants: List<AndroidVariant>
) : SyntheticFileGenerator(project) {
private val cachedJetFiles by lazy {
val supportV4 = supportV4Available()
generateSyntheticJetFiles(generateSyntheticFiles(true, supportV4))
}
override val layoutXmlFileManager: CliAndroidLayoutXmlFileManager by lazy {
CliAndroidLayoutXmlFileManager(project, manifestPath, variants)
}
public override fun getSyntheticFiles(): List<KtFile> = cachedJetFiles
override fun extractLayoutResources(files: List<PsiFile>): List<AndroidResource> {
val resources = arrayListOf<AndroidResource>()
val handler = AndroidXmlHandler { id, tag ->
resources += parseAndroidResource(id, tag) { tag ->
resolveFqClassNameForView(tag)
}
}
for (file in files) {
try {
val inputStream = ByteArrayInputStream(file.virtualFile.contentsToByteArray())
layoutXmlFileManager.saxParser.parse(inputStream, handler)
} catch (e: Throwable) {
LOG.error(e)
}
}
return filterDuplicates(resources)
}
override fun checkIfClassExist(fqName: String): Boolean {
val scope = GlobalSearchScope.allScope(project)
val psiElementFinders = project.getExtensions(PsiElementFinder.EP_NAME).filter { it is PsiElementFinderImpl }
for (finder in psiElementFinders) {
val clazz = finder.findClass(fqName, scope)
if (clazz != null) return true
}
return false
}
override fun parseAndroidWidget(id: String, tag: String, fqNameResolver: (String) -> String?): AndroidResource {
val fqName = fqNameResolver(tag)
val invalidType = if (fqName != null) null else tag
val type = fqName ?: (if ('.' in tag) tag else AndroidConst.VIEW_FQNAME)
return AndroidWidget(id, type, invalidType)
}
private companion object {
private val LOG: Logger = Logger.getInstance(CliSyntheticFileGenerator::class.java)
}
}
@@ -1,258 +0,0 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider.Result
import com.intellij.psi.util.CachedValuesManager
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.KotlinStringWriter
import org.jetbrains.kotlin.android.synthetic.escapeAndroidIdentifier
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.types.Flexibility
public class AndroidSyntheticFile(val name: String, val contents: String) {
companion object {
fun create(name: String, vararg contents: String) = AndroidSyntheticFile(name, contents.joinToString("\n\n"))
}
}
public abstract class SyntheticFileGenerator(protected val project: Project) {
public class NoAndroidManifestFound : Exception("No android manifest file found in project root")
public abstract val layoutXmlFileManager: AndroidLayoutXmlFileManager
public abstract fun getSyntheticFiles(): List<KtFile>
protected open fun generateSyntheticFiles(generateCommonFiles: Boolean, supportV4: Boolean): List<AndroidSyntheticFile> {
val commonFiles = if (generateCommonFiles) generateCommonFiles(supportV4) else listOf()
return layoutXmlFileManager.getLayoutXmlFiles().flatMap { variantData ->
variantData.flatMap { entry ->
val files = entry.value
val resources = extractLayoutResources(files)
val layoutName = entry.key
arrayListOf<AndroidSyntheticFile>().apply {
this += renderMainLayoutFiles(variantData.variant, layoutName, resources, supportV4)
this += renderViewLayoutFiles(variantData.variant, layoutName, resources)
}
}
}.filterNotNull() + commonFiles
}
private fun generateCommonFiles(supportV4: Boolean): List<AndroidSyntheticFile> {
val renderSyntheticFile = renderSyntheticFile("clearCache") {
writePackage(AndroidConst.SYNTHETIC_PACKAGE)
writeAndroidImports()
writeClearCacheFunction(AndroidConst.ACTIVITY_FQNAME)
writeClearCacheFunction(AndroidConst.FRAGMENT_FQNAME)
if (supportV4) {
writeClearCacheFunction(AndroidConst.SUPPORT_FRAGMENT_FQNAME)
}
}
val clearCacheFile = renderSyntheticFile
return listOf(clearCacheFile, TOOLS_FILE)
}
protected abstract fun extractLayoutResources(files: List<PsiFile>): List<AndroidResource>
protected abstract fun checkIfClassExist(fqName: String): Boolean
private fun renderMainLayoutFiles(
variant: AndroidVariant,
layoutName: String,
resources: List<AndroidResource>,
supportV4: Boolean
): List<AndroidSyntheticFile> {
return renderLayoutFile(variant, layoutName + AndroidConst.LAYOUT_POSTFIX, escapeAndroidIdentifier(layoutName), resources) {
val properties = it.mainProperties.toArrayList()
if (supportV4) properties.addAll(it.mainPropertiesForSupportV4)
properties
}
}
private fun renderViewLayoutFiles(
variant: AndroidVariant,
layoutName: String,
resources: List<AndroidResource>
): List<AndroidSyntheticFile> {
return renderLayoutFile(variant, layoutName + AndroidConst.VIEW_LAYOUT_POSTFIX,
escapeAndroidIdentifier(layoutName) + ".view", resources) { it.viewProperties }
}
private fun renderLayoutFile(
variant: AndroidVariant,
filename: String,
layoutName: String,
resources: List<AndroidResource>,
properties: (AndroidResource) -> List<Pair<String, String>>): List<AndroidSyntheticFile> {
fun render(defaultVariant: Boolean = false): AndroidSyntheticFile {
val fullFilename = (if (defaultVariant) "" else "${variant.name}_") + filename
return renderSyntheticFile(fullFilename) {
val packageName = if (defaultVariant)
AndroidConst.SYNTHETIC_PACKAGE + "." + layoutName
else
AndroidConst.SYNTHETIC_PACKAGE + '.' + variant.name + '.' + layoutName
writePackage(packageName)
writeAndroidImports()
for (res in resources) {
properties(res).forEach { property ->
if (defaultVariant) {
val deprecatedText = "Use the property from the 'main' variant instead"
val import = AndroidConst.SYNTHETIC_PACKAGE + '.' + variant.name + '.' + layoutName + '.' + res.id
writeText("@Deprecated(\"$deprecatedText\", ReplaceWith(\"${res.id}\", \"$import\"))")
}
writeSyntheticProperty(property.first, res, property.second)
}
}
}
}
return if (variant.isMainVariant) listOf(render(), render(true)) else listOf(render())
}
private fun renderSyntheticFile(filename: String, init: KotlinStringWriter.() -> Unit): AndroidSyntheticFile {
val stringWriter = KotlinStringWriter()
stringWriter.init()
return AndroidSyntheticFile(filename, stringWriter.toStringBuffer().toString())
}
private fun KotlinStringWriter.writeAndroidImports() {
ANDROID_IMPORTS.forEach { writeImport(it) }
writeEmptyLine()
}
private fun KotlinStringWriter.writeSyntheticProperty(receiver: String, resource: AndroidResource, stubCall: String) {
val className = if (isFromSupportV4Package(receiver)) resource.supportClassName else resource.className
val cast = if (resource.className != AndroidConst.VIEW_FQNAME) " as? $className" else ""
val body = arrayListOf("return $stubCall$cast")
// Annotation on wrong widget type
if (resource is AndroidWidget && resource.invalidType != null) {
writeText("@${INVALID_WIDGET_TYPE_ANNOTATION_FQNAME.asString()}(\"${resource.invalidType}\")")
}
writeImmutableExtensionProperty(receiver,
name = resource.id,
retType = "$EXPLICIT_FLEXIBLE_CLASS_NAME<$className, $className?>",
getterBody = body)
}
private fun KotlinStringWriter.writeClearCacheFunction(receiver: String) {
writeText("public fun $receiver.${AndroidConst.CLEAR_FUNCTION_NAME}() {}\n")
}
protected fun <T> cachedValue(result: () -> Result<T>): CachedValue<T> {
return CachedValuesManager.getManager(project).createCachedValue(result, false)
}
private fun isFromSupportV4Package(fqName: String): Boolean {
return fqName.startsWith(AndroidConst.SUPPORT_V4_PACKAGE)
}
protected fun resolveFqClassNameForView(tag: String): String? {
if (tag.contains('.')) {
if (!checkIfClassExist(tag)) {
return null
}
return tag
}
for (pkg in AndroidConst.FQNAME_RESOLVE_PACKAGES) {
val fqName = "$pkg.$tag"
if (checkIfClassExist(fqName)) {
return fqName
}
}
return null
}
protected fun parseAndroidResource(id: String, tag: String, fqNameResolver: (String) -> String?): AndroidResource {
return when (tag) {
"fragment" -> AndroidFragment(id)
"include" -> AndroidWidget(id, AndroidConst.VIEW_FQNAME)
else -> parseAndroidWidget(id, tag, fqNameResolver)
}
}
protected abstract fun parseAndroidWidget(id: String, tag: String, fqNameResolver: (String) -> String?): AndroidResource
protected fun supportV4Available(): Boolean = checkIfClassExist(AndroidConst.SUPPORT_FRAGMENT_FQNAME)
protected fun filterDuplicates(resources: List<AndroidResource>): List<AndroidResource> {
val resourceMap = linkedMapOf<String, AndroidResource>()
val resourcesToExclude = hashSetOf<String>()
for (res in resources) {
if (resourceMap.contains(res.id)) {
val existing = resourceMap[res.id]!!
if (!res.sameClass(existing)) {
resourcesToExclude.add(res.id)
} else if (res is AndroidWidget && existing.className != res.className && existing.className != AndroidConst.VIEW_FQNAME) {
// Widgets with the same id but different types exist.
resourceMap.put(res.id, AndroidWidget(res.id, AndroidConst.VIEW_FQNAME))
}
}
else resourceMap.put(res.id, res)
}
resourcesToExclude.forEach { resourceMap.remove(it) }
return resourceMap.values().toList()
}
protected fun generateSyntheticJetFiles(files: List<AndroidSyntheticFile>): List<KtFile> {
val psiManager = PsiManager.getInstance(project)
val applicationPackage = layoutXmlFileManager.androidModule?.applicationPackage
return files.mapIndexed { index, syntheticFile ->
val fileName = AndroidConst.SYNTHETIC_FILENAME_PREFIX + syntheticFile.name + ".kt"
val virtualFile = LightVirtualFile(fileName, syntheticFile.contents)
val jetFile = psiManager.findFile(virtualFile) as KtFile
if (applicationPackage != null) {
jetFile.putUserData(AndroidConst.ANDROID_USER_PACKAGE, applicationPackage)
}
jetFile
}
}
public companion object {
private val EXPLICIT_FLEXIBLE_PACKAGE = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.packageFqName.asString()
private val EXPLICIT_FLEXIBLE_CLASS_NAME = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.relativeClassName.asString()
private val ANDROID_IMPORTS = listOf(Flexibility.FLEXIBLE_TYPE_CLASSIFIER.asSingleFqName().asString())
private val INVALID_WIDGET_TYPE_ANNOTATION = "InvalidWidgetType"
public val INVALID_WIDGET_TYPE_ANNOTATION_TYPE_PARAMETER: String = "type"
public val INVALID_WIDGET_TYPE_ANNOTATION_FQNAME: FqName = FqName("$EXPLICIT_FLEXIBLE_PACKAGE.$INVALID_WIDGET_TYPE_ANNOTATION")
private val TOOLS_FILE = AndroidSyntheticFile.create("tools",
"package $EXPLICIT_FLEXIBLE_PACKAGE",
"class $EXPLICIT_FLEXIBLE_CLASS_NAME<L, U>",
"annotation class $INVALID_WIDGET_TYPE_ANNOTATION(public val $INVALID_WIDGET_TYPE_ANNOTATION_TYPE_PARAMETER: String)")
}
}
@@ -16,7 +16,17 @@
package org.jetbrains.kotlin.android.synthetic.res
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
class AndroidVariant(val name: String, val resDirectories: List<String>) {
val packageName: String = name
@@ -28,61 +38,52 @@ class AndroidVariant(val name: String, val resDirectories: List<String>) {
}
}
public class AndroidModule(val applicationPackage: String, val variants: List<AndroidVariant>) {
class AndroidModule(val applicationPackage: String, val variants: List<AndroidVariant>) {
override fun equals(other: Any?) = other is AndroidModule && applicationPackage == other.applicationPackage
override fun hashCode() = applicationPackage.hashCode()
}
public abstract class AndroidResource(val id: String) {
public abstract val className: String
public open val supportClassName: String
get() = className
sealed class AndroidResource(val id: String, val sourceElement: PsiElement?) {
open fun sameClass(other: AndroidResource): Boolean = false
public abstract val mainProperties: List<Pair<String, String>>
public open val mainPropertiesForSupportV4: List<Pair<String, String>> = listOf()
public open val viewProperties: List<Pair<String, String>> = listOf()
public open fun sameClass(other: AndroidResource): Boolean = false
}
public class AndroidWidget(
id: String,
override val className: String,
val invalidType: String? = null // When widget type is invalid, this value is the widget tag name
) : AndroidResource(id) {
private companion object {
val MAIN_PROPERTIES = listOf(
AndroidConst.ACTIVITY_FQNAME to "findViewById(0)",
AndroidConst.FRAGMENT_FQNAME to "getView().findViewById(0)")
val MAIN_PROPERTIES_SUPPORT_V4 = listOf(AndroidConst.SUPPORT_FRAGMENT_FQNAME to "getView().findViewById(0)")
val VIEW_PROPERTIES = listOf(AndroidConst.VIEW_FQNAME to "findViewById(0)")
class Widget(
id: String,
val xmlType: String,
sourceElement: PsiElement?
) : AndroidResource(id, sourceElement) {
override fun sameClass(other: AndroidResource) = other is Widget
}
override val mainProperties = MAIN_PROPERTIES
override val mainPropertiesForSupportV4 = MAIN_PROPERTIES_SUPPORT_V4
override val viewProperties = VIEW_PROPERTIES
override fun sameClass(other: AndroidResource) = other is AndroidWidget
class Fragment(id: String, sourceElement: PsiElement?) : AndroidResource(id, sourceElement) {
override fun sameClass(other: AndroidResource) = other is Fragment
}
}
public class AndroidFragment(id: String) : AndroidResource(id) {
private companion object {
val MAIN_PROPERTIES = listOf(
AndroidConst.ACTIVITY_FQNAME to "getFragmentManager().findFragmentById(0)",
AndroidConst.FRAGMENT_FQNAME to "getFragmentManager().findFragmentById(0)")
fun <T> cachedValue(project: Project, result: () -> CachedValueProvider.Result<T>): CachedValue<T> {
return CachedValuesManager.getManager(project).createCachedValue(result, false)
}
val MAIN_PROPERTIES_SUPPORT_V4 = listOf(
AndroidConst.SUPPORT_FRAGMENT_FQNAME to "getFragmentManager().findFragmentById(0)",
AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME to "getSupportFragmentManager().findFragmentById(0)")
class ResolvedWidget(val widget: AndroidResource.Widget, val viewClassDescriptor: ClassDescriptor?) {
val isErrorType: Boolean
get() = viewClassDescriptor == null
val errorType: String?
get() = if (isErrorType) widget.xmlType else null
}
fun AndroidResource.Widget.resolve(module: ModuleDescriptor): ResolvedWidget {
fun resolve(fqName: String) = module.findClassAcrossModuleDependencies(ClassId.topLevel(FqName(fqName)))
if ('.' in xmlType) {
return ResolvedWidget(this, resolve(xmlType))
}
override val className = AndroidConst.FRAGMENT_FQNAME
override val supportClassName = AndroidConst.SUPPORT_FRAGMENT_FQNAME
for (packageName in AndroidConst.FQNAME_RESOLVE_PACKAGES) {
val classDescriptor = resolve("$packageName.$xmlType")
if (classDescriptor != null) {
return ResolvedWidget(this, classDescriptor)
}
}
override val mainProperties = MAIN_PROPERTIES
override val mainPropertiesForSupportV4 = MAIN_PROPERTIES_SUPPORT_V4
override fun sameClass(other: AndroidResource) = other is AndroidFragment
return ResolvedWidget(this, null)
}
@@ -0,0 +1,137 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.android.synthetic.res
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.android.synthetic.AndroidConst
import org.jetbrains.kotlin.android.synthetic.descriptors.AndroidSyntheticPackageFragmentDescriptor
import org.jetbrains.kotlin.android.synthetic.descriptors.SyntheticElementResolveContext
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.makeNullable
private class XmlSourceElement(override val psi: PsiElement) : PsiSourceElement
internal fun genClearCacheFunction(packageFragmentDescriptor: PackageFragmentDescriptor, receiverType: KotlinType): FunctionDescriptor {
val function = object : AndroidSyntheticFunction, SimpleFunctionDescriptorImpl(
packageFragmentDescriptor,
null,
Annotations.EMPTY,
Name.identifier(AndroidConst.CLEAR_FUNCTION_NAME),
CallableMemberDescriptor.Kind.SYNTHESIZED,
SourceElement.NO_SOURCE) {}
val unitType = packageFragmentDescriptor.builtIns.unitType
function.initialize(receiverType, null, emptyList(), emptyList(), unitType, Modality.FINAL, Visibilities.PUBLIC)
return function
}
internal fun genPropertyForWidget(
packageFragmentDescriptor: AndroidSyntheticPackageFragmentDescriptor,
receiverType: KotlinType,
resolvedWidget: ResolvedWidget,
context: SyntheticElementResolveContext
): PropertyDescriptor {
val sourceEl = resolvedWidget.widget.sourceElement?.let { XmlSourceElement(it) } ?: SourceElement.NO_SOURCE
val classDescriptor = resolvedWidget.viewClassDescriptor
val type = classDescriptor?.let {
val defaultType = classDescriptor.defaultType
if (defaultType.constructor.parameters.isEmpty())
defaultType
else
KotlinTypeImpl.create(Annotations.EMPTY, classDescriptor, false,
defaultType.constructor.parameters.map { StarProjectionImpl(it) })
} ?: context.viewType
return genProperty(resolvedWidget.widget.id, receiverType, type, packageFragmentDescriptor, sourceEl, resolvedWidget.errorType)
}
internal fun genPropertyForFragment(
packageFragmentDescriptor: PackageFragmentDescriptor,
receiverType: KotlinType,
type: KotlinType,
fragment: AndroidResource.Fragment
): PropertyDescriptor {
val sourceElement = fragment.sourceElement?.let { XmlSourceElement(it) } ?: SourceElement.NO_SOURCE
return genProperty(fragment.id, receiverType, type, packageFragmentDescriptor, sourceElement, null)
}
private fun genProperty(
id: String,
receiverType: KotlinType,
type: KotlinType,
containingDeclaration: DeclarationDescriptor,
sourceElement: SourceElement,
errorType: String?
): PropertyDescriptor {
val property = object : AndroidSyntheticProperty, PropertyDescriptorImpl(
containingDeclaration,
null,
Annotations.EMPTY,
Modality.FINAL,
Visibilities.PUBLIC,
false,
Name.identifier(id),
CallableMemberDescriptor.Kind.SYNTHESIZED,
sourceElement,
false,
false) {
override val errorType = errorType
}
val flexibleType = DelegatingFlexibleType.create(type, type.makeNullable(), FlexibleTypeCapabilities.NONE)
property.setType(
flexibleType,
emptyList<TypeParameterDescriptor>(),
null,
receiverType)
val getter = PropertyGetterDescriptorImpl(
property,
Annotations.EMPTY,
Modality.FINAL,
Visibilities.PUBLIC,
false,
false,
false,
CallableMemberDescriptor.Kind.SYNTHESIZED,
null,
SourceElement.NO_SOURCE)
getter.initialize(null)
property.initialize(getter, null)
return property
}
interface AndroidSyntheticFunction
interface AndroidSyntheticProperty {
val errorType: String?
val isErrorType: Boolean
get() = errorType != null
}