Refactoring: rename android-compiler-plugin to android-extensions-compiler

This commit is contained in:
Natalia Ukhorskaya
2016-03-11 15:59:29 +03:00
parent 93161be199
commit 7e6fa8ef17
159 changed files with 84 additions and 84 deletions
@@ -0,0 +1 @@
org.jetbrains.kotlin.android.synthetic.AndroidCommandLineProcessor
@@ -0,0 +1 @@
org.jetbrains.kotlin.android.synthetic.AndroidComponentRegistrar
@@ -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
import com.intellij.mock.MockProject
import com.intellij.openapi.extensions.Extensions
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenExtension
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidOnDestroyClassBuilderInterceptorExtension
import org.jetbrains.kotlin.android.synthetic.diagnostic.AndroidExtensionPropertiesCallChecker
import org.jetbrains.kotlin.android.synthetic.diagnostic.DefaultErrorMessagesAndroid
import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager
import org.jetbrains.kotlin.android.synthetic.res.AndroidVariant
import org.jetbrains.kotlin.android.synthetic.res.CliAndroidLayoutXmlFileManager
import org.jetbrains.kotlin.android.synthetic.res.CliAndroidPackageFragmentProviderExtension
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.compiler.plugin.CliOption
import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.config.CompilerConfiguration
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.StorageComponentContainerContributor
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
object AndroidConfigurationKeys {
val VARIANT: CompilerConfigurationKey<List<String>> = CompilerConfigurationKey.create<List<String>>("Android build variant")
val PACKAGE: CompilerConfigurationKey<String> = CompilerConfigurationKey.create<String>("application package fq name")
}
class AndroidCommandLineProcessor : CommandLineProcessor {
companion object {
val ANDROID_COMPILER_PLUGIN_ID: String = "org.jetbrains.kotlin.android"
val VARIANT_OPTION: CliOption = CliOption("variant", "<name;path>", "Android build variant", allowMultipleOccurrences = true)
val PACKAGE_OPTION: CliOption = CliOption("package", "<fq name>", "Application package")
}
override val pluginId: String = ANDROID_COMPILER_PLUGIN_ID
override val pluginOptions: Collection<CliOption> = listOf(VARIANT_OPTION, PACKAGE_OPTION)
override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) {
when (option) {
VARIANT_OPTION -> {
val paths = configuration.getList(AndroidConfigurationKeys.VARIANT).toMutableList()
paths.add(value)
configuration.put(AndroidConfigurationKeys.VARIANT, paths)
}
PACKAGE_OPTION -> configuration.put(AndroidConfigurationKeys.PACKAGE, value)
else -> throw CliOptionProcessingException("Unknown option: ${option.name}")
}
}
}
class AndroidComponentRegistrar : ComponentRegistrar {
override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
val applicationPackage = configuration.get(AndroidConfigurationKeys.PACKAGE)
val variants = configuration.get(AndroidConfigurationKeys.VARIANT)?.mapNotNull { parseVariant(it) } ?: emptyList()
if (variants.isNotEmpty() && !applicationPackage.isNullOrBlank()) {
val layoutXmlFileManager = CliAndroidLayoutXmlFileManager(project, applicationPackage!!, variants)
project.registerService(AndroidLayoutXmlFileManager::class.java, layoutXmlFileManager)
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())
}
}
private fun parseVariant(s: String): AndroidVariant? {
val parts = s.split(';')
if (parts.size < 2) return null
return AndroidVariant(parts[0], parts.drop(1))
}
}
class AndroidExtensionPropertiesComponentContainerContributor : StorageComponentContainerContributor {
override fun addDeclarations(container: StorageComponentContainer, platform: TargetPlatform) {
if (platform is JvmPlatform) {
container.useInstance(AndroidExtensionPropertiesCallChecker())
}
}
}
@@ -0,0 +1,78 @@
/*
* 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
object AndroidConst {
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"
val CLASS_ATTRIBUTE_NO_NAMESPACE: String = "class"
val ID_DECLARATION_PREFIX = "@+id/"
val ID_USAGE_PREFIX = "@id/"
val XML_ID_PREFIXES = arrayOf(ID_DECLARATION_PREFIX, ID_USAGE_PREFIX)
val CLEAR_FUNCTION_NAME = "clearFindViewByIdCache"
//TODO FqName / ClassId
val VIEW_FQNAME = "android.view.View"
val VIEWSTUB_FQNAME = "android.view.ViewStub"
val ACTIVITY_FQNAME = "android.app.Activity"
val FRAGMENT_FQNAME = "android.app.Fragment"
val SUPPORT_V4_PACKAGE = "android.support.v4"
val SUPPORT_FRAGMENT_FQNAME = "$SUPPORT_V4_PACKAGE.app.Fragment"
val SUPPORT_FRAGMENT_ACTIVITY_FQNAME = "$SUPPORT_V4_PACKAGE.app.FragmentActivity"
val IGNORED_XML_WIDGET_TYPES = setOf("requestFocus", "merge", "tag", "check", "blink")
val FQNAME_RESOLVE_PACKAGES = listOf("android.widget", "android.webkit", "android.view")
}
fun androidIdToName(id: String): String? {
for (prefix in AndroidConst.XML_ID_PREFIXES) {
if (id.startsWith(prefix)) {
return id.substring(prefix.length)
}
}
return null
}
fun isWidgetTypeIgnored(xmlType: String): Boolean {
return (xmlType.isEmpty() || xmlType in AndroidConst.IGNORED_XML_WIDGET_TYPES)
}
internal fun <T> List<T>.forEachUntilLast(operation: (T) -> Unit) {
val lastIndex = lastIndex
forEachIndexed { i, t ->
if (i < lastIndex) {
operation(t)
}
}
}
@@ -0,0 +1,58 @@
/*
* 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 org.xml.sax.Attributes
import org.xml.sax.helpers.DefaultHandler
import java.util.HashMap
class AndroidXmlHandler(private val elementCallback: (String, String) -> Unit) : DefaultHandler() {
override fun startDocument() {
super.startDocument()
}
override fun endDocument() {
super.endDocument()
}
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
if (isWidgetTypeIgnored(localName)) return
val attributesMap = attributes.toMap()
val idAttribute = attributesMap[AndroidConst.ID_ATTRIBUTE_NO_NAMESPACE]
val widgetType = attributesMap[AndroidConst.CLASS_ATTRIBUTE_NO_NAMESPACE] ?: localName
val name = idAttribute?.let { androidIdToName(idAttribute) }
if (name != null) elementCallback(name, widgetType)
}
override fun endElement(uri: String?, localName: String, qName: String) {
}
}
fun Attributes.toMap(): HashMap<String, String> {
val res = HashMap<String, String>()
for (index in 0..length - 1) {
val attrName = getLocalName(index)!!
val attrVal = getValue(index)!!
res[attrName] = attrVal
}
return res
}
@@ -0,0 +1,391 @@
/*
* 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.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
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
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.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.types.lowerIfFlexible
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PRIVATE
import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PUBLIC
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
enum class AndroidClassType(className: String, val supportsCache: Boolean = false, val fragment: Boolean = false) {
ACTIVITY(AndroidConst.ACTIVITY_FQNAME, supportsCache = true),
FRAGMENT(AndroidConst.FRAGMENT_FQNAME, supportsCache = true, fragment = true),
SUPPORT_FRAGMENT_ACTIVITY(AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME, supportsCache = true),
SUPPORT_FRAGMENT(AndroidConst.SUPPORT_FRAGMENT_FQNAME, supportsCache = true, fragment = true),
VIEW(AndroidConst.VIEW_FQNAME),
UNKNOWN("");
val internalClassName: String = className.replace('.', '/')
companion object {
fun getClassType(descriptor: ClassifierDescriptor): AndroidClassType {
fun getClassTypeInternal(name: String): AndroidClassType? = when (name) {
AndroidConst.ACTIVITY_FQNAME -> AndroidClassType.ACTIVITY
AndroidConst.FRAGMENT_FQNAME -> AndroidClassType.FRAGMENT
AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY
AndroidConst.SUPPORT_FRAGMENT_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT
AndroidConst.VIEW_FQNAME -> AndroidClassType.VIEW
else -> null
}
if (descriptor is LazyJavaClassDescriptor) {
val androidClassType = getClassTypeInternal(DescriptorUtils.getFqName(descriptor).asString())
if (androidClassType != null) return androidClassType
}
else if (descriptor is LazyClassDescriptor) { // For tests (FakeActivity)
val androidClassType = getClassTypeInternal(DescriptorUtils.getFqName(descriptor).toString())
if (androidClassType != null) return androidClassType
}
for (supertype in descriptor.typeConstructor.supertypes) {
val declarationDescriptor = supertype.constructor.declarationDescriptor
if (declarationDescriptor != null) {
val androidClassType = getClassType(declarationDescriptor)
if (androidClassType != AndroidClassType.UNKNOWN) return androidClassType
}
}
return AndroidClassType.UNKNOWN
}
}
}
class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
companion object {
private val PROPERTY_NAME = "_\$_findViewCache"
private val CACHED_FIND_VIEW_BY_ID_METHOD_NAME = "_\$_findCachedViewById"
val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache"
val ON_DESTROY_METHOD_NAME = "onDestroyView"
fun isCacheSupported(receiverDescriptor: ClassDescriptor, descriptor: PropertyDescriptor? = null): Boolean {
val receiverIsKotlinClass = receiverDescriptor.source is KotlinSourceElement
return receiverIsKotlinClass && when (descriptor) {
is AndroidSyntheticProperty -> !descriptor.alwaysCastToView
else -> true
}
}
}
private class SyntheticPartsGenerateContext(
val classBuilder: ClassBuilder,
val state: GenerationState,
val descriptor: ClassDescriptor,
val classOrObject: KtClassOrObject,
val androidClassType: AndroidClassType)
override fun applyProperty(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
val resultingDescriptor = resolvedCall.resultingDescriptor
return if (resultingDescriptor is PropertyDescriptor) {
return generateSyntheticPropertyCall(receiver, resolvedCall, c, resultingDescriptor)
}
else null
}
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 null
}
private fun generateSyntheticFunctionCall(
receiver: StackValue,
resolvedCall: ResolvedCall<*>,
c: ExpressionCodegenExtension.Context,
functionDescriptor: FunctionDescriptor
): StackValue? {
if (functionDescriptor !is AndroidSyntheticFunction) return null
if (functionDescriptor.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME) return null
val receiverDescriptor = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null
if (!isCacheSupported(receiverDescriptor)) return StackValue.functionCall(Type.VOID_TYPE) {}
val androidClassType = AndroidClassType.getClassType(receiverDescriptor)
if (androidClassType == AndroidClassType.UNKNOWN) return null
return StackValue.functionCall(Type.VOID_TYPE) {
val bytecodeClassName = c.typeMapper.mapType(receiverDescriptor).internalName
receiver.put(c.typeMapper.mapType(receiverDescriptor), it)
it.invokevirtual(bytecodeClassName, CLEAR_CACHE_METHOD_NAME, "()V", false)
}
}
private fun generateSyntheticPropertyCall(
receiver: StackValue,
resolvedCall: ResolvedCall<*>,
c: ExpressionCodegenExtension.Context,
descriptor: PropertyDescriptor
): StackValue? {
if (descriptor !is AndroidSyntheticProperty) return null
val packageFragment = descriptor.containingDeclaration as? AndroidSyntheticPackageFragmentDescriptor ?: return null
val androidPackage = packageFragment.packageData.moduleData.module.applicationPackage
val receiverDescriptor = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null
val androidClassType = AndroidClassType.getClassType(receiverDescriptor)
return SyntheticProperty(receiver, c.typeMapper, descriptor, receiverDescriptor, androidClassType, androidPackage)
}
private class SyntheticProperty(
val receiver: StackValue,
val typeMapper: KotlinTypeMapper,
val propertyDescriptor: PropertyDescriptor,
val receiverDescriptor: ClassDescriptor,
val androidClassType: AndroidClassType,
val androidPackage: String
) : StackValue(typeMapper.mapType(propertyDescriptor.returnType!!)) {
override fun putSelector(type: Type, v: InstructionAdapter) {
val returnTypeString = typeMapper.mapType(propertyDescriptor.type.lowerIfFlexible()).className
if (AndroidConst.FRAGMENT_FQNAME == returnTypeString || AndroidConst.SUPPORT_FRAGMENT_FQNAME == returnTypeString) {
return putSelectorForFragment(v)
}
if (androidClassType.supportsCache && isCacheSupported(receiverDescriptor, propertyDescriptor)) {
val declarationDescriptorType = typeMapper.mapType(receiverDescriptor)
receiver.put(declarationDescriptorType, v)
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", propertyDescriptor.name.asString(), "I")
v.invokevirtual(declarationDescriptorType.internalName, CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", false)
}
else {
when (androidClassType) {
AndroidClassType.ACTIVITY, AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY, AndroidClassType.VIEW -> {
receiver.put(Type.getType("L${androidClassType.internalClassName};"), v)
getResourceId(v)
v.invokevirtual(androidClassType.internalClassName, "findViewById", "(I)Landroid/view/View;", false)
}
AndroidClassType.FRAGMENT, AndroidClassType.SUPPORT_FRAGMENT -> {
receiver.put(Type.getType("L${androidClassType.internalClassName};"), v)
v.invokevirtual(androidClassType.internalClassName, "getView", "()Landroid/view/View;", false)
getResourceId(v)
v.invokevirtual("android/view/View", "findViewById", "(I)Landroid/view/View;", false)
}
else -> throw IllegalStateException("Invalid Android class type: $androidClassType") // Should never occur
}
}
v.checkcast(this.type)
}
private fun putSelectorForFragment(v: InstructionAdapter) {
receiver.put(Type.getType("L${androidClassType.internalClassName};"), v)
when (androidClassType) {
AndroidClassType.ACTIVITY, AndroidClassType.FRAGMENT -> {
v.invokevirtual(androidClassType.internalClassName, "getFragmentManager", "()Landroid/app/FragmentManager;", false)
getResourceId(v)
v.invokevirtual("android/app/FragmentManager", "findFragmentById", "(I)Landroid/app/Fragment;", false)
}
AndroidClassType.SUPPORT_FRAGMENT -> {
v.invokevirtual(androidClassType.internalClassName, "getFragmentManager", "()Landroid/support/v4/app/FragmentManager;", false)
getResourceId(v)
v.invokevirtual("android/support/v4/app/FragmentManager", "findFragmentById", "(I)Landroid/support/v4/app/Fragment;", false)
}
AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY -> {
v.invokevirtual(androidClassType.internalClassName, "getSupportFragmentManager", "()Landroid/support/v4/app/FragmentManager;", false)
getResourceId(v)
v.invokevirtual("android/support/v4/app/FragmentManager", "findFragmentById", "(I)Landroid/support/v4/app/Fragment;", false)
}
else -> throw IllegalStateException("Invalid Android class type: $androidClassType") // Should never occur
}
v.checkcast(this.type)
}
fun getResourceId(v: InstructionAdapter) {
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", propertyDescriptor.name.asString(), "I")
}
}
private fun ResolvedCall<*>.getReceiverDeclarationDescriptor(): ClassifierDescriptor? {
return (extensionReceiver as ReceiverValue).type.constructor.declarationDescriptor
}
override fun generateClassSyntheticParts(
classBuilder: ClassBuilder,
state: GenerationState,
classOrObject: KtClassOrObject,
descriptor: ClassDescriptor
) {
if (descriptor.kind != ClassKind.CLASS || descriptor.isInner || DescriptorUtils.isLocal(descriptor)) return
// Do not generate anything if class is not supported
val androidClassType = AndroidClassType.getClassType(descriptor)
if (androidClassType == AndroidClassType.UNKNOWN) return
val context = SyntheticPartsGenerateContext(classBuilder, state, descriptor, classOrObject, androidClassType)
context.generateCachedFindViewByIdFunction()
context.generateClearCacheFunction()
if (androidClassType.fragment) {
val classMembers = descriptor.unsubstitutedMemberScope.getContributedDescriptors()
val onDestroy = classMembers.firstOrNull { it is FunctionDescriptor && it.isOnDestroyFunction() }
if (onDestroy == null) {
context.generateOnDestroyFunctionForFragment()
}
}
classBuilder.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, PROPERTY_NAME, "Ljava/util/HashMap;", null, null)
}
private fun FunctionDescriptor.isOnDestroyFunction(): Boolean {
return kind == CallableMemberDescriptor.Kind.DECLARATION
&& name.asString() == ON_DESTROY_METHOD_NAME
&& (visibility == Visibilities.INHERITED || visibility == Visibilities.PUBLIC)
&& (valueParameters.isEmpty())
&& (typeParameters.isEmpty())
}
// This generates a simple onDestroy(): Unit = super.onDestroy() function.
// CLEAR_CACHE_METHOD_NAME() method call will be inserted in ClassBuilder interceptor.
private fun SyntheticPartsGenerateContext.generateOnDestroyFunctionForFragment() {
val methodVisitor = classBuilder.newMethod(JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, ON_DESTROY_METHOD_NAME, "()V", null, null)
methodVisitor.visitCode()
val iv = InstructionAdapter(methodVisitor)
val classType = state.typeMapper.mapClass(descriptor)
iv.load(0, classType)
iv.invokespecial(state.typeMapper.mapClass(descriptor.getSuperClassOrAny()).internalName, ON_DESTROY_METHOD_NAME, "()V", false)
iv.areturn(Type.VOID_TYPE)
FunctionCodegen.endVisit(methodVisitor, ON_DESTROY_METHOD_NAME, classOrObject)
}
private fun SyntheticPartsGenerateContext.generateClearCacheFunction() {
val methodVisitor = classBuilder.newMethod(JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, CLEAR_CACHE_METHOD_NAME, "()V", null, null)
methodVisitor.visitCode()
val iv = InstructionAdapter(methodVisitor)
val classType = state.typeMapper.mapClass(descriptor)
val className = classType.internalName
fun loadCache() {
iv.load(0, classType)
iv.getfield(className, PROPERTY_NAME, "Ljava/util/HashMap;")
}
loadCache()
val lCacheIsNull = Label()
iv.ifnull(lCacheIsNull)
loadCache()
iv.invokevirtual("java/util/HashMap", "clear", "()V", false)
iv.visitLabel(lCacheIsNull)
iv.areturn(Type.VOID_TYPE)
FunctionCodegen.endVisit(methodVisitor, CLEAR_CACHE_METHOD_NAME, classOrObject)
}
private fun SyntheticPartsGenerateContext.generateCachedFindViewByIdFunction() {
val classType = state.typeMapper.mapClass(descriptor)
val className = classType.internalName
val viewType = Type.getObjectType("android/view/View")
val methodVisitor = classBuilder.newMethod(
JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", null, null)
methodVisitor.visitCode()
val iv = InstructionAdapter(methodVisitor)
fun loadCache() {
iv.load(0, classType)
iv.getfield(className, PROPERTY_NAME, "Ljava/util/HashMap;")
}
fun loadId() = iv.load(1, Type.INT_TYPE)
// Get cache property
loadCache()
val lCacheNonNull = Label()
iv.ifnonnull(lCacheNonNull)
// Init cache if null
iv.load(0, classType)
iv.anew(Type.getType("Ljava/util/HashMap;"))
iv.dup()
iv.invokespecial("java/util/HashMap", "<init>", "()V", false)
iv.putfield(className, PROPERTY_NAME, "Ljava/util/HashMap;")
// Get View from cache
iv.visitLabel(lCacheNonNull)
loadCache()
loadId()
iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)
iv.invokevirtual("java/util/HashMap", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", false)
iv.checkcast(viewType)
iv.store(2, viewType)
val lViewNonNull = Label()
iv.load(2, viewType)
iv.ifnonnull(lViewNonNull)
// Resolve View via findViewById if not in cache
iv.load(0, classType)
when (androidClassType) {
AndroidClassType.ACTIVITY, AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY, AndroidClassType.VIEW -> {
loadId()
iv.invokevirtual(className, "findViewById", "(I)Landroid/view/View;", false)
}
AndroidClassType.FRAGMENT, AndroidClassType.SUPPORT_FRAGMENT -> {
iv.invokevirtual(className, "getView", "()Landroid/view/View;", false)
loadId()
iv.invokevirtual("android/view/View", "findViewById", "(I)Landroid/view/View;", false)
}
else -> throw IllegalStateException("Can't generate code for $androidClassType")
}
iv.store(2, viewType)
// Store resolved View in cache
loadCache()
loadId()
iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)
iv.load(2, viewType)
iv.invokevirtual("java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false)
iv.pop()
iv.visitLabel(lViewNonNull)
iv.load(2, viewType)
iv.areturn(viewType)
FunctionCodegen.endVisit(methodVisitor, CACHED_FIND_VIEW_BY_ID_METHOD_NAME, classOrObject)
}
}
@@ -0,0 +1,127 @@
/*
* 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.codegen
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.ClassBuilderFactory
import org.jetbrains.kotlin.codegen.DelegatingClassBuilder
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class AndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension {
override fun interceptClassBuilderFactory(
interceptedFactory: ClassBuilderFactory,
bindingContext: BindingContext,
diagnostics: DiagnosticSink
): ClassBuilderFactory {
return AndroidOnDestroyClassBuilderFactory(interceptedFactory, bindingContext)
}
private inner class AndroidOnDestroyClassBuilderFactory(
private val delegateFactory: ClassBuilderFactory,
val bindingContext: BindingContext
) : ClassBuilderFactory {
override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder {
return AndroidOnDestroyCollectorClassBuilder(delegateFactory.newClassBuilder(origin), bindingContext)
}
override fun getClassBuilderMode() = delegateFactory.classBuilderMode
override fun asText(builder: ClassBuilder?): String? {
return delegateFactory.asText((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder)
}
override fun asBytes(builder: ClassBuilder?): ByteArray? {
return delegateFactory.asBytes((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder)
}
override fun close() {
delegateFactory.close()
}
}
private inner class AndroidOnDestroyCollectorClassBuilder(
internal val delegateClassBuilder: ClassBuilder,
val bindingContext: BindingContext
) : DelegatingClassBuilder() {
private var currentClass: KtClass? = null
private var currentClassName: String? = null
override fun getDelegate() = delegateClassBuilder
override fun defineClass(
origin: PsiElement?,
version: Int,
access: Int,
name: String,
signature: String?,
superName: String,
interfaces: Array<out String>
) {
if (origin is KtClass) {
currentClass = origin
currentClassName = name
}
super.defineClass(origin, version, access, name, signature, superName, interfaces)
}
override fun newMethod(
origin: JvmDeclarationOrigin,
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
return object : MethodVisitor(Opcodes.ASM5, super.newMethod(origin, access, name, desc, signature, exceptions)) {
override fun visitInsn(opcode: Int) {
if (opcode == Opcodes.RETURN) {
generateClearCacheMethodCall()
}
super.visitInsn(opcode)
}
private fun generateClearCacheMethodCall() {
if (name != AndroidExpressionCodegenExtension.ON_DESTROY_METHOD_NAME || currentClass == null) return
if (Type.getArgumentTypes(desc).size != 0) return
if (Type.getReturnType(desc) != Type.VOID_TYPE) return
val classType = currentClassName?.let { Type.getObjectType(it) } ?: return
val descriptor = bindingContext.get(BindingContext.CLASS, currentClass) as? ClassDescriptor ?: return
val androidClassType = AndroidClassType.getClassType(descriptor)
if (!androidClassType.fragment) return
val iv = InstructionAdapter(this)
iv.load(0, classType)
iv.invokevirtual(currentClassName, AndroidExpressionCodegenExtension.CLEAR_CACHE_METHOD_NAME, "()V", false)
}
}
}
}
}
@@ -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, context)
}
}
}
}
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,59 @@
/*
* 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.*
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,
val subpackages: List<PackageFragmentDescriptor> = emptyList(),
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)
}
}
}
@@ -0,0 +1,57 @@
/*
* 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.diagnostic
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.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
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 containingPackage = propertyDescriptor.containingDeclaration as? AndroidSyntheticPackageFragmentDescriptor ?: return
val androidSyntheticProperty = propertyDescriptor as? AndroidSyntheticProperty ?: return
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))
}
}
@@ -0,0 +1,43 @@
/*
* 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.diagnostic
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
class DefaultErrorMessagesAndroid : DefaultErrorMessages.Extension {
private companion object {
val MAP = DiagnosticFactoryToRendererMap("Android")
init {
MAP.put(ErrorsAndroid.SYNTHETIC_INVALID_WIDGET_TYPE,
"Widget has an invalid type ''{0}''. Please specify the fully qualified widget class name in XML",
Renderers.TO_STRING)
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")
}
}
override fun getMap() = MAP
}
@@ -0,0 +1,38 @@
/*
* 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.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;
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() {
{
Errors.Initializer.initializeFactoryNames(ErrorsAndroid.class);
}
};
}
@@ -0,0 +1,134 @@
/*
* 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.module.Module
import com.intellij.openapi.module.ModuleServiceManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
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.*
class AndroidVariantData(val variant: AndroidVariant, private val layouts: Map<String, List<PsiFile>>): Map<String, List<PsiFile>> by layouts
class AndroidModuleData(val module: AndroidModule, private val variants: List<AndroidVariantData>): Iterable<AndroidVariantData> by variants {
companion object {
val EMPTY = AndroidModuleData(AndroidModule("android", listOf()), listOf())
}
}
abstract class AndroidLayoutXmlFileManager(val project: Project) {
abstract val androidModule: AndroidModule?
open fun propertyToXmlAttributes(propertyDescriptor: PropertyDescriptor): List<PsiElement> = listOf()
open fun getModuleData(): AndroidModuleData {
val androidModule = androidModule ?: return AndroidModuleData.EMPTY
return AndroidModuleData(androidModule, androidModule.variants.map { getVariantData(it) })
}
fun getVariantData(variant: AndroidVariant): AndroidVariantData {
val psiManager = PsiManager.getInstance(project)
val fileManager = VirtualFileManager.getInstance()
fun VirtualFile.getAllChildren(): List<VirtualFile> {
val allChildren = arrayListOf<VirtualFile>()
val currentChildren = children ?: emptyArray()
for (child in currentChildren) {
if (child.isDirectory) {
allChildren.addAll(child.getAllChildren())
}
else {
allChildren.add(child)
}
}
return allChildren
}
val resDirectories = variant.resDirectories.map { fileManager.findFileByUrl("file://$it") }
val allChildren = resDirectories.flatMap { it?.getAllChildren() ?: listOf() }
val allLayoutFiles = allChildren.filter { it.parent.name.startsWith("layout") && it.name.toLowerCase().endsWith(".xml") }
val allLayoutPsiFiles = allLayoutFiles.fold(ArrayList<PsiFile>(allLayoutFiles.size)) { list, file ->
val psiFile = psiManager.findFile(file)
if (psiFile != null && psiFile.parent != null) {
list += psiFile
}
list
}
val layoutNameToXmlFiles = allLayoutPsiFiles
.groupBy { it.name.substringBeforeLast('.') }
.mapValues { it.value.sortedBy { it.parent!!.name.length } }
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 {
fun getInstance(module: Module): AndroidLayoutXmlFileManager? {
val service = ModuleServiceManager.getService(module, AndroidLayoutXmlFileManager::class.java)
return service ?: module.getComponent(AndroidLayoutXmlFileManager::class.java)
}
}
}
@@ -0,0 +1,102 @@
/*
* 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?
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 lazyContext = LazySyntheticElementResolveContext(module, storageManager)
val allPackageDescriptors = arrayListOf<PackageFragmentDescriptor>()
val packagesToLookupInCompletion = arrayListOf<PackageFragmentDescriptor>()
// Packages with synthetic properties
for (variantData in moduleData) {
for ((layoutName, layouts) in variantData) {
fun createPackageFragment(fqName: String, forView: Boolean, isDeprecated: Boolean = false) {
val resources = layoutXmlFileManager.extractResources(layouts, module)
val packageData = AndroidSyntheticPackageData(moduleData, forView, isDeprecated, resources)
val packageDescriptor = AndroidSyntheticPackageFragmentDescriptor(
module, FqName(fqName), packageData, lazyContext, storageManager)
packagesToLookupInCompletion += packageDescriptor
allPackageDescriptors += packageDescriptor
}
val packageFqName = AndroidConst.SYNTHETIC_PACKAGE + '.' + variantData.variant.name + '.' + layoutName
createPackageFragment(packageFqName, false)
createPackageFragment(packageFqName + ".view", true)
}
}
// Empty middle packages
AndroidConst.SYNTHETIC_SUBPACKAGES.forEachUntilLast { s ->
allPackageDescriptors += PredefinedPackageFragmentDescriptor(s, module, storageManager)
}
for (variantData in moduleData) {
val fqName = AndroidConst.SYNTHETIC_PACKAGE + '.' + variantData.variant.name
allPackageDescriptors += PredefinedPackageFragmentDescriptor(fqName, module, storageManager)
}
// Package with clearFindViewByIdCache()
AndroidConst.SYNTHETIC_SUBPACKAGES.last().let { s ->
val packageDescriptor = PredefinedPackageFragmentDescriptor(s, module, storageManager, packagesToLookupInCompletion) { descriptor ->
lazyContext().getWidgetReceivers(false).map { genClearCacheFunction(descriptor, it) }
}
packagesToLookupInCompletion += packageDescriptor
allPackageDescriptors += packageDescriptor
}
return AndroidSyntheticPackageFragmentProvider(allPackageDescriptors)
}
}
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()
}
@@ -0,0 +1,66 @@
/*
* 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.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
class CliAndroidLayoutXmlFileManager(
project: Project,
private val applicationPackage: String,
private val variants: List<AndroidVariant>
) : AndroidLayoutXmlFileManager(project) {
private companion object {
val LOG = Logger.getInstance(CliAndroidLayoutXmlFileManager::class.java)
}
override val androidModule = AndroidModule(applicationPackage, variants)
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()
saxFactory.isNamespaceAware = true
return saxFactory.newSAXParser()
}
}
@@ -0,0 +1,27 @@
/*
* 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.components.ServiceManager
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.ModuleInfo
class CliAndroidPackageFragmentProviderExtension : AndroidPackageFragmentProviderExtension() {
override fun getLayoutXmlFileManager(project: Project, moduleInfo: ModuleInfo?): AndroidLayoutXmlFileManager? {
return ServiceManager.getService(project, AndroidLayoutXmlFileManager::class.java)
}
}
@@ -0,0 +1,89 @@
/*
* 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.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
val isMainVariant: Boolean
get() = name == "main"
companion object {
fun createMainVariant(resDirectories: List<String>) = AndroidVariant("main", resDirectories)
}
}
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()
}
sealed class AndroidResource(val id: String, val sourceElement: PsiElement?) {
open fun sameClass(other: AndroidResource): Boolean = false
class Widget(
id: String,
val xmlType: String,
sourceElement: PsiElement?
) : AndroidResource(id, sourceElement) {
override fun sameClass(other: AndroidResource) = other is Widget
}
class Fragment(id: String, sourceElement: PsiElement?) : AndroidResource(id, sourceElement) {
override fun sameClass(other: AndroidResource) = other is Fragment
}
}
fun <T> cachedValue(project: Project, result: () -> CachedValueProvider.Result<T>): CachedValue<T> {
return CachedValuesManager.getManager(project).createCachedValue(result, false)
}
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))
}
for (packageName in AndroidConst.FQNAME_RESOLVE_PACKAGES) {
val classDescriptor = resolve("$packageName.$xmlType")
if (classDescriptor != null) {
return ResolvedWidget(this, classDescriptor)
}
}
return ResolvedWidget(this, null)
}
@@ -0,0 +1,145 @@
/*
* 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.descriptorUtil.fqNameUnsafe
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, context, resolvedWidget.errorType)
}
internal fun genPropertyForFragment(
packageFragmentDescriptor: PackageFragmentDescriptor,
receiverType: KotlinType,
type: KotlinType,
fragment: AndroidResource.Fragment,
context: SyntheticElementResolveContext
): PropertyDescriptor {
val sourceElement = fragment.sourceElement?.let { XmlSourceElement(it) } ?: SourceElement.NO_SOURCE
return genProperty(fragment.id, receiverType, type, packageFragmentDescriptor, sourceElement, context, null)
}
private fun genProperty(
id: String,
receiverType: KotlinType,
type: KotlinType,
containingDeclaration: DeclarationDescriptor,
sourceElement: SourceElement,
context: SyntheticElementResolveContext,
errorType: String?
): PropertyDescriptor {
val alwaysCastToView = type.constructor.declarationDescriptor?.fqNameUnsafe?.asString() == AndroidConst.VIEWSTUB_FQNAME
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
override val alwaysCastToView = alwaysCastToView
}
val actualType = if (alwaysCastToView) context.viewType else type
val flexibleType = DelegatingFlexibleType.create(actualType, actualType.makeNullable(), FlexibleTypeCapabilities.NONE)
property.setType(
flexibleType,
emptyList<TypeParameterDescriptor>(),
null,
receiverType)
val getter = PropertyGetterDescriptorImpl(
property,
Annotations.EMPTY,
Modality.FINAL,
Visibilities.PUBLIC,
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 alwaysCastToView: Boolean
val isErrorType: Boolean
get() = errorType != null
}