Create descriptors directly in Android Extensions (compiler plugin)
This commit is contained in:
+3
-18
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-10
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
-85
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
+19
-19
@@ -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
|
||||
}
|
||||
|
||||
+87
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -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
|
||||
}
|
||||
}
|
||||
+61
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-15
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
+3
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -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() {
|
||||
|
||||
+44
-2
@@ -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)
|
||||
|
||||
+106
@@ -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()
|
||||
}
|
||||
+29
-2
@@ -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()
|
||||
|
||||
+9
-14
@@ -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)
|
||||
}
|
||||
}
|
||||
-91
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
-258
@@ -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)")
|
||||
}
|
||||
|
||||
}
|
||||
+45
-44
@@ -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)
|
||||
}
|
||||
+137
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user