diff --git a/idea/src/META-INF/android.xml b/idea/src/META-INF/android.xml index 46b3918e194..01e6be4bf4f 100644 --- a/idea/src/META-INF/android.xml +++ b/idea/src/META-INF/android.xml @@ -74,7 +74,7 @@ - + diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt index f7573581f47..4e3fbec660b 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt @@ -19,11 +19,12 @@ package org.jetbrains.kotlin.android.synthetic import com.intellij.mock.MockProject import com.intellij.openapi.extensions.Extensions import com.intellij.openapi.project.Project +import kotlinx.android.extensions.CacheImplementation import org.jetbrains.kotlin.android.parcel.ParcelableCodegenExtension import org.jetbrains.kotlin.android.parcel.ParcelableDeclarationChecker import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension -import org.jetbrains.kotlin.android.synthetic.codegen.AndroidOnDestroyClassBuilderInterceptorExtension import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidExtensionsExpressionCodegenExtension +import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidOnDestroyClassBuilderInterceptorExtension import org.jetbrains.kotlin.android.synthetic.diagnostic.AndroidExtensionPropertiesCallChecker import org.jetbrains.kotlin.android.synthetic.diagnostic.DefaultErrorMessagesAndroid import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager @@ -49,18 +50,21 @@ import org.jetbrains.kotlin.android.synthetic.codegen.ParcelableClinitClassBuild import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension object AndroidConfigurationKeys { - val VARIANT: CompilerConfigurationKey> = CompilerConfigurationKey.create>("Android build variant") - val PACKAGE: CompilerConfigurationKey = CompilerConfigurationKey.create("application package fq name") - val EXPERIMENTAL: CompilerConfigurationKey = CompilerConfigurationKey.create("enable experimental features") + val VARIANT = CompilerConfigurationKey.create>("Android build variant") + val PACKAGE = CompilerConfigurationKey.create("application package fq name") + val EXPERIMENTAL = CompilerConfigurationKey.create("enable experimental features") + val DEFAULT_CACHE_IMPL = CompilerConfigurationKey.create("default cache implementation") } class AndroidCommandLineProcessor : CommandLineProcessor { companion object { val ANDROID_COMPILER_PLUGIN_ID: String = "org.jetbrains.kotlin.android" - val VARIANT_OPTION: CliOption = CliOption("variant", "", "Android build variant", allowMultipleOccurrences = true) - val PACKAGE_OPTION: CliOption = CliOption("package", "", "Application package") - val EXPERIMENTAL_OPTION: CliOption = CliOption("experimental", "true/false", "Enable experimental features", required = false) + val VARIANT_OPTION = CliOption("variant", "", "Android build variant", allowMultipleOccurrences = true) + val PACKAGE_OPTION = CliOption("package", "", "Application package") + val EXPERIMENTAL_OPTION = CliOption("experimental", "true/false", "Enable experimental features", required = false) + val DEFAULT_CACHE_IMPL_OPTION = CliOption( + "defaultCacheImplementation", "hashMap/sparseArray/none", "Default cache implementation for module", required = false) /* This option is just for saving Android Extensions status in Kotlin facet. It should not be supported from CLI. */ val ENABLED_OPTION: CliOption = CliOption("enabled", "true/false", "Enable Android Extensions", required = false) @@ -68,13 +72,15 @@ class AndroidCommandLineProcessor : CommandLineProcessor { override val pluginId: String = ANDROID_COMPILER_PLUGIN_ID - override val pluginOptions: Collection = listOf(VARIANT_OPTION, PACKAGE_OPTION, EXPERIMENTAL_OPTION) + override val pluginOptions: Collection + = listOf(VARIANT_OPTION, PACKAGE_OPTION, EXPERIMENTAL_OPTION, DEFAULT_CACHE_IMPL_OPTION) override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) { when (option) { VARIANT_OPTION -> configuration.appendList(AndroidConfigurationKeys.VARIANT, value) PACKAGE_OPTION -> configuration.put(AndroidConfigurationKeys.PACKAGE, value) EXPERIMENTAL_OPTION -> configuration.put(AndroidConfigurationKeys.EXPERIMENTAL, value) + DEFAULT_CACHE_IMPL_OPTION -> configuration.put(AndroidConfigurationKeys.DEFAULT_CACHE_IMPL, value) else -> throw CliOptionProcessingException("Unknown option: ${option.name}") } } @@ -87,12 +93,19 @@ class AndroidComponentRegistrar : ComponentRegistrar { SyntheticResolveExtension.registerExtension(project, ParcelableResolveExtension()) ClassBuilderInterceptorExtension.registerExtension(project, ParcelableClinitClassBuilderInterceptorExtension()) } + + fun parseCacheImplementationType(s: String?): CacheImplementation = when (s) { + "sparseArray" -> CacheImplementation.SPARSE_ARRAY + "none" -> CacheImplementation.NO_CACHE + else -> CacheImplementation.DEFAULT + } } override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { val applicationPackage = configuration.get(AndroidConfigurationKeys.PACKAGE) val variants = configuration.get(AndroidConfigurationKeys.VARIANT)?.mapNotNull { parseVariant(it) } ?: emptyList() val isExperimental = configuration.get(AndroidConfigurationKeys.EXPERIMENTAL) == "true" + val globalCacheImpl = parseCacheImplementationType(configuration.get(AndroidConfigurationKeys.DEFAULT_CACHE_IMPL)) if (isExperimental) { registerParcelExtensions(project) @@ -102,10 +115,10 @@ class AndroidComponentRegistrar : ComponentRegistrar { val layoutXmlFileManager = CliAndroidLayoutXmlFileManager(project, applicationPackage!!, variants) project.registerService(AndroidLayoutXmlFileManager::class.java, layoutXmlFileManager) - ExpressionCodegenExtension.registerExtension(project, CliAndroidExtensionsExpressionCodegenExtension(isExperimental)) + ExpressionCodegenExtension.registerExtension(project, CliAndroidExtensionsExpressionCodegenExtension(isExperimental, globalCacheImpl)) StorageComponentContainerContributor.registerExtension(project, AndroidExtensionPropertiesComponentContainerContributor()) Extensions.getRootArea().getExtensionPoint(DefaultErrorMessages.Extension.EP_NAME).registerExtension(DefaultErrorMessagesAndroid()) - ClassBuilderInterceptorExtension.registerExtension(project, AndroidOnDestroyClassBuilderInterceptorExtension()) + ClassBuilderInterceptorExtension.registerExtension(project, CliAndroidOnDestroyClassBuilderInterceptorExtension(globalCacheImpl)) PackageFragmentProviderExtension.registerExtension(project, CliAndroidPackageFragmentProviderExtension(isExperimental)) } } diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AbstractAndroidExtensionsExpressionCodegenExtension.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AbstractAndroidExtensionsExpressionCodegenExtension.kt index 446ab4daeb4..a665ae8fda9 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AbstractAndroidExtensionsExpressionCodegenExtension.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AbstractAndroidExtensionsExpressionCodegenExtension.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.android.synthetic.codegen +import kotlinx.android.extensions.CacheImplementation import kotlinx.android.extensions.CacheImplementation.NO_CACHE import org.jetbrains.kotlin.android.synthetic.AndroidConst import org.jetbrains.kotlin.android.synthetic.codegen.AndroidContainerType.LAYOUT_CONTAINER @@ -32,6 +33,7 @@ import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny @@ -51,9 +53,7 @@ abstract class AbstractAndroidExtensionsExpressionCodegenExtension : ExpressionC val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache" val ON_DESTROY_METHOD_NAME = "onDestroyView" - fun shouldCacheResource(resource: PropertyDescriptor): Boolean { - return (resource as? AndroidSyntheticProperty)?.shouldBeCached == true - } + fun shouldCacheResource(resource: PropertyDescriptor) = (resource as? AndroidSyntheticProperty)?.shouldBeCached == true } private class SyntheticPartsGenerateContext( @@ -63,7 +63,10 @@ abstract class AbstractAndroidExtensionsExpressionCodegenExtension : ExpressionC val classOrObject: KtClassOrObject, val containerOptions: ContainerOptionsProxy) - protected abstract fun isExperimental(clazz: KtClassOrObject): Boolean + protected abstract fun isExperimental(element: KtElement?): Boolean + protected abstract fun getGlobalCacheImpl(element: KtElement?): CacheImplementation + + private fun ContainerOptionsProxy.getCacheOrDefault(element: KtElement?) = this.cache ?: getGlobalCacheImpl(element) override fun applyProperty(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? { val resultingDescriptor = resolvedCall.resultingDescriptor @@ -79,7 +82,7 @@ abstract class AbstractAndroidExtensionsExpressionCodegenExtension : ExpressionC return if (targetCallable.name.asString() == AndroidConst.CLEAR_FUNCTION_NAME) { val container = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null - generateClearFindViewByIdCacheFunctionCall(receiver, container, c) + generateClearFindViewByIdCacheFunctionCall(receiver, resolvedCall, container, c) } else { null @@ -88,12 +91,13 @@ abstract class AbstractAndroidExtensionsExpressionCodegenExtension : ExpressionC private fun generateClearFindViewByIdCacheFunctionCall( receiver: StackValue, + resolvedCall: ResolvedCall<*>, container: ClassDescriptor, c: ExpressionCodegenExtension.Context ): StackValue? { val containerOptions = ContainerOptionsProxy.create(container) - if (!containerOptions.cache.hasCache) { + if (!containerOptions.getCacheOrDefault(resolvedCall.call.calleeExpression).hasCache) { return StackValue.functionCall(Type.VOID_TYPE) {} } @@ -119,7 +123,8 @@ abstract class AbstractAndroidExtensionsExpressionCodegenExtension : ExpressionC val container = resolvedCall.getReceiverDeclarationDescriptor() as? ClassDescriptor ?: return null val containerOptions = ContainerOptionsProxy.create(container) - return ResourcePropertyStackValue(receiver, c.typeMapper, resource, container, containerOptions, androidPackage) + return ResourcePropertyStackValue(receiver, c.typeMapper, resource, container, + containerOptions, androidPackage, getGlobalCacheImpl(resolvedCall.call.calleeExpression)) } private fun ResolvedCall<*>.getReceiverDeclarationDescriptor(): ClassifierDescriptor? { @@ -134,7 +139,7 @@ abstract class AbstractAndroidExtensionsExpressionCodegenExtension : ExpressionC if (container.kind != ClassKind.CLASS || container.isInner || DescriptorUtils.isLocal(container)) return val containerOptions = ContainerOptionsProxy.create(container) - if (containerOptions.cache == NO_CACHE) return + if (containerOptions.getCacheOrDefault(targetClass) == NO_CACHE) return if (containerOptions.containerType == LAYOUT_CONTAINER && !isExperimental(targetClass)) { return @@ -184,7 +189,7 @@ abstract class AbstractAndroidExtensionsExpressionCodegenExtension : ExpressionC val iv = InstructionAdapter(methodVisitor) val containerType = state.typeMapper.mapClass(container) - val cacheImpl = CacheMechanism.get(containerOptions, iv, containerType) + val cacheImpl = CacheMechanism.get(containerOptions.getCacheOrDefault(classOrObject), iv, containerType) cacheImpl.loadCache() val lCacheIsNull = Label() @@ -199,7 +204,7 @@ abstract class AbstractAndroidExtensionsExpressionCodegenExtension : ExpressionC } private fun SyntheticPartsGenerateContext.generateCacheField() { - val cacheImpl = CacheMechanism.getType(containerOptions) + val cacheImpl = CacheMechanism.getType(containerOptions.getCacheOrDefault(classOrObject)) classBuilder.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, PROPERTY_NAME, cacheImpl.descriptor, null, null) } @@ -213,7 +218,7 @@ abstract class AbstractAndroidExtensionsExpressionCodegenExtension : ExpressionC methodVisitor.visitCode() val iv = InstructionAdapter(methodVisitor) - val cacheImpl = CacheMechanism.get(containerOptions, iv, containerAsmType) + val cacheImpl = CacheMechanism.get(containerOptions.getCacheOrDefault(classOrObject), iv, containerAsmType) fun loadId() = iv.load(1, Type.INT_TYPE) diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidOnDestroyClassBuilderInterceptorExtension.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidOnDestroyClassBuilderInterceptorExtension.kt index 38ad611ba52..e9a2186afa7 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidOnDestroyClassBuilderInterceptorExtension.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidOnDestroyClassBuilderInterceptorExtension.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.android.synthetic.codegen import com.intellij.psi.PsiElement +import kotlinx.android.extensions.CacheImplementation import org.jetbrains.kotlin.android.synthetic.codegen.AbstractAndroidExtensionsExpressionCodegenExtension.Companion.ON_DESTROY_METHOD_NAME import org.jetbrains.kotlin.android.synthetic.descriptors.ContainerOptionsProxy import org.jetbrains.kotlin.codegen.ClassBuilder @@ -30,9 +31,9 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import org.jetbrains.kotlin.android.synthetic.codegen.AbstractAndroidExtensionsExpressionCodegenExtension.Companion.CLEAR_CACHE_METHOD_NAME +import org.jetbrains.kotlin.psi.KtElement -class AndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension { - +abstract class AbstractAndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension { override fun interceptClassBuilderFactory( interceptedFactory: ClassBuilderFactory, bindingContext: BindingContext, @@ -41,6 +42,8 @@ class AndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptor return AndroidOnDestroyClassBuilderFactory(interceptedFactory, bindingContext) } + abstract fun getGlobalCacheImpl(element: KtElement): CacheImplementation + private inner class AndroidOnDestroyClassBuilderFactory( private val delegateFactory: ClassBuilderFactory, val bindingContext: BindingContext @@ -108,6 +111,7 @@ class AndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptor } private fun generateClearCacheMethodCall() { + val currentClass = currentClass if (name != ON_DESTROY_METHOD_NAME || currentClass == null) return if (Type.getArgumentTypes(desc).isNotEmpty()) return if (Type.getReturnType(desc) != Type.VOID_TYPE) return @@ -116,7 +120,7 @@ class AndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptor val container = bindingContext.get(BindingContext.CLASS, currentClass) ?: return val entityOptions = ContainerOptionsProxy.create(container) - if (!entityOptions.containerType.isFragment || !entityOptions.cache.hasCache) return + if (!entityOptions.containerType.isFragment || !(entityOptions.cache ?: getGlobalCacheImpl(currentClass)).hasCache) return val iv = InstructionAdapter(this) iv.load(0, containerType) diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CacheMechanisms.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CacheMechanisms.kt index 75a8fff6452..aff68de21a6 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CacheMechanisms.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CacheMechanisms.kt @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.android.synthetic.codegen import kotlinx.android.extensions.CacheImplementation -import org.jetbrains.kotlin.android.synthetic.descriptors.ContainerOptionsProxy import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import org.jetbrains.kotlin.android.synthetic.codegen.AbstractAndroidExtensionsExpressionCodegenExtension.Companion.PROPERTY_NAME @@ -39,16 +38,16 @@ interface CacheMechanism { fun putViewToCache(getView: () -> Unit) companion object { - fun getType(containerOptions: ContainerOptionsProxy): Type { - return Type.getObjectType(when (containerOptions.cache) { + fun getType(cacheImpl: CacheImplementation): Type { + return Type.getObjectType(when (cacheImpl) { CacheImplementation.SPARSE_ARRAY -> "android.util.SparseArray" CacheImplementation.HASH_MAP -> HashMap::class.java.canonicalName CacheImplementation.NO_CACHE -> throw IllegalArgumentException("Container should support cache") }.replace('.', '/')) } - fun get(containerOptions: ContainerOptionsProxy, iv: InstructionAdapter, containerType: Type): CacheMechanism { - return when (containerOptions.cache) { + fun get(cacheImpl: CacheImplementation, iv: InstructionAdapter, containerType: Type): CacheMechanism { + return when (cacheImpl) { CacheImplementation.HASH_MAP -> HashMapCacheMechanism(iv, containerType) CacheImplementation.SPARSE_ARRAY -> SparseArrayCacheMechanism(iv, containerType) CacheImplementation.NO_CACHE -> throw IllegalArgumentException("Container should support cache") diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CliAndroidExtensionsExpressionCodegenExtension.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CliAndroidExtensionsExpressionCodegenExtension.kt index 14bdfcd1103..8e76513b2f9 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CliAndroidExtensionsExpressionCodegenExtension.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CliAndroidExtensionsExpressionCodegenExtension.kt @@ -16,8 +16,13 @@ package org.jetbrains.kotlin.android.synthetic.codegen -import org.jetbrains.kotlin.psi.KtClassOrObject +import kotlinx.android.extensions.CacheImplementation +import org.jetbrains.kotlin.psi.KtElement -class CliAndroidExtensionsExpressionCodegenExtension(val isExperimental: Boolean) : AbstractAndroidExtensionsExpressionCodegenExtension() { - override fun isExperimental(clazz: KtClassOrObject) = isExperimental +class CliAndroidExtensionsExpressionCodegenExtension( + val isExperimental: Boolean, + private val globalCacheImpl: CacheImplementation +) : AbstractAndroidExtensionsExpressionCodegenExtension() { + override fun isExperimental(element: KtElement?) = isExperimental + override fun getGlobalCacheImpl(element: KtElement?) = globalCacheImpl } \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CliAndroidOnDestroyClassBuilderInterceptorExtension.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CliAndroidOnDestroyClassBuilderInterceptorExtension.kt new file mode 100644 index 00000000000..e4fd4c341db --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CliAndroidOnDestroyClassBuilderInterceptorExtension.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2017 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 kotlinx.android.extensions.CacheImplementation +import org.jetbrains.kotlin.psi.KtElement + +class CliAndroidOnDestroyClassBuilderInterceptorExtension( + private val globalCacheImpl: CacheImplementation +) : AbstractAndroidOnDestroyClassBuilderInterceptorExtension() { + override fun getGlobalCacheImpl(element: KtElement) = globalCacheImpl +} \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/ResourcePropertyStackValue.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/ResourcePropertyStackValue.kt index a17cadd86ed..e32af1cb08e 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/ResourcePropertyStackValue.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/ResourcePropertyStackValue.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.android.synthetic.codegen +import kotlinx.android.extensions.CacheImplementation import org.jetbrains.kotlin.android.synthetic.AndroidConst import org.jetbrains.kotlin.android.synthetic.codegen.AbstractAndroidExtensionsExpressionCodegenExtension.Companion.shouldCacheResource import org.jetbrains.kotlin.android.synthetic.codegen.AbstractAndroidExtensionsExpressionCodegenExtension.Companion.CACHED_FIND_VIEW_BY_ID_METHOD_NAME @@ -35,7 +36,8 @@ class ResourcePropertyStackValue( val resource: PropertyDescriptor, val container: ClassDescriptor, private val containerOptions: ContainerOptionsProxy, - private val androidPackage: String + private val androidPackage: String, + private val globalCacheImpl: CacheImplementation ) : StackValue(typeMapper.mapType(resource.returnType!!)) { private val containerType get() = containerOptions.containerType @@ -51,7 +53,7 @@ class ResourcePropertyStackValue( val syntheticProperty = resource as AndroidSyntheticProperty - if (containerOptions.cache.hasCache && shouldCacheResource(resource)) { + if ((containerOptions.cache ?: globalCacheImpl).hasCache && shouldCacheResource(resource)) { val declarationDescriptorType = typeMapper.mapType(container) receiver.put(declarationDescriptorType, v) diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/descriptors/ContainerOptionsProxy.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/descriptors/ContainerOptionsProxy.kt index bda1274c0b1..464dbdf29d2 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/descriptors/ContainerOptionsProxy.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/descriptors/ContainerOptionsProxy.kt @@ -28,13 +28,11 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.constants.EnumValue import org.jetbrains.kotlin.resolve.source.KotlinSourceElement -class ContainerOptionsProxy(val containerType: AndroidContainerType, val cache: CacheImplementation) { +class ContainerOptionsProxy(val containerType: AndroidContainerType, val cache: CacheImplementation?) { companion object { private val CONTAINER_OPTIONS_FQNAME = FqName(ContainerOptions::class.java.canonicalName) private val CACHE_NAME = ContainerOptions::cache.name - private val DEFAULT_CACHE_IMPL = SPARSE_ARRAY - fun create(container: ClassDescriptor): ContainerOptionsProxy { if (container.kind != ClassKind.CLASS) { return ContainerOptionsProxy(AndroidContainerType.UNKNOWN, NO_CACHE) @@ -49,24 +47,24 @@ class ContainerOptionsProxy(val containerType: AndroidContainerType, val cache: val supportsCache = container.source is KotlinSourceElement && containerType.doesSupportCache return ContainerOptionsProxy( containerType, - if (supportsCache) DEFAULT_CACHE_IMPL else NO_CACHE) + if (supportsCache) null else NO_CACHE) // `null` here means "use global cache implementation setting" } - val cache = anno.getEnumValue(CACHE_NAME, DEFAULT_CACHE_IMPL) { valueOf(it) } + val cache = anno.getEnumValue(CACHE_NAME) { valueOf(it) } return ContainerOptionsProxy(containerType, cache) } } } -private fun > AnnotationDescriptor.getEnumValue(name: String, defaultValue: E, factory: (String) -> E): E { - val valueName = (allValueArguments[Name.identifier(name)] as? EnumValue)?.value?.name?.asString() ?: defaultValue.name +private fun > AnnotationDescriptor.getEnumValue(name: String, factory: (String) -> E): E? { + val valueName = (allValueArguments[Name.identifier(name)] as? EnumValue)?.value?.name?.asString() ?: return null return try { factory(valueName) } catch (e: IllegalArgumentException) { // Enum.valueOf() may throw this - defaultValue + null } -} +} \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-idea/android-extensions-idea.iml b/plugins/android-extensions/android-extensions-idea/android-extensions-idea.iml index 824738f3711..d27956575ff 100644 --- a/plugins/android-extensions/android-extensions-idea/android-extensions-idea.iml +++ b/plugins/android-extensions/android-extensions-idea/android-extensions-idea.iml @@ -25,5 +25,6 @@ + \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/AndroidExtensionsProjectResolverExtension.kt b/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/AndroidExtensionsProjectResolverExtension.kt index 4d8b7b2f923..4c68b3b3311 100644 --- a/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/AndroidExtensionsProjectResolverExtension.kt +++ b/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/AndroidExtensionsProjectResolverExtension.kt @@ -19,14 +19,12 @@ package org.jetbrains.kotlin.android.synthetic.idea import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.ProjectKeys import com.intellij.openapi.externalSystem.model.project.ModuleData -import com.intellij.openapi.externalSystem.model.project.ProjectData import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.util.Key import org.gradle.api.Project import org.gradle.tooling.model.idea.IdeaModule -import org.gradle.tooling.model.idea.IdeaProject -import org.jetbrains.kotlin.android.synthetic.AndroidCommandLineProcessor import org.jetbrains.kotlin.android.synthetic.AndroidCommandLineProcessor.Companion.ANDROID_COMPILER_PLUGIN_ID +import org.jetbrains.kotlin.android.synthetic.AndroidCommandLineProcessor.Companion.DEFAULT_CACHE_IMPL_OPTION import org.jetbrains.kotlin.android.synthetic.AndroidCommandLineProcessor.Companion.EXPERIMENTAL_OPTION import org.jetbrains.kotlin.android.synthetic.AndroidCommandLineProcessor.Companion.ENABLED_OPTION import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments @@ -46,14 +44,22 @@ var DataNode.hasAndroidExtensionsPlugin: Boolean var DataNode.isExperimental: Boolean by NotNullableUserDataProperty(Key.create("ANDROID_EXTENSIONS_IS_EXPERIMENTAL"), false) +private const val DEFAULT_CACHE_IMPLEMENTATION_DEFAULT_VALUE = "hashMap" + +var DataNode.defaultCacheImplementation: String + by NotNullableUserDataProperty(Key.create("ANDROID_EXTENSIONS_DEFAULT_CACHE_IMPL"), + DEFAULT_CACHE_IMPLEMENTATION_DEFAULT_VALUE) + interface AndroidExtensionsGradleModel : Serializable { val hasAndroidExtensionsPlugin: Boolean val isExperimental: Boolean + val defaultCacheImplementation: String } class AndroidExtensionsGradleModelImpl( override val hasAndroidExtensionsPlugin: Boolean, - override val isExperimental: Boolean + override val isExperimental: Boolean, + override val defaultCacheImplementation: String ) : AndroidExtensionsGradleModel @Suppress("unused") @@ -66,6 +72,7 @@ class AndroidExtensionsProjectResolverExtension : AbstractProjectResolverExtensi ideModule.hasAndroidExtensionsPlugin = androidExtensionsModel.hasAndroidExtensionsPlugin ideModule.isExperimental = androidExtensionsModel.isExperimental + ideModule.defaultCacheImplementation = androidExtensionsModel.defaultCacheImplementation super.populateModuleExtraModels(gradleModule, ideModule) } @@ -82,7 +89,9 @@ class AndroidExtensionsModelBuilderService : ModelBuilderService { override fun buildAll(modelName: String?, project: Project): Any { val androidExtensionsPlugin = project.plugins.findPlugin("kotlin-android-extensions") - val isExperimental = project.extensions.findByName("androidExtensions")?.let { ext -> + val androidExtensionsExtension = project.extensions.findByName("androidExtensions") + + val isExperimental = androidExtensionsExtension?.let { ext -> val isExperimentalMethod = ext::class.java.methods .firstOrNull { it.name == "isExperimental" && it.parameterCount == 0 } ?: return@let false @@ -90,7 +99,20 @@ class AndroidExtensionsModelBuilderService : ModelBuilderService { isExperimentalMethod.invoke(ext) as? Boolean } ?: false - return AndroidExtensionsGradleModelImpl(androidExtensionsPlugin != null, isExperimental) + val defaultCacheImplementation = androidExtensionsExtension?.let { ext -> + val defaultCacheImplementationMethod = ext::class.java.methods.firstOrNull { + it.name == "getDefaultCacheImplementation" && it.parameterCount == 0 + } ?: return@let DEFAULT_CACHE_IMPLEMENTATION_DEFAULT_VALUE + + val enumValue = defaultCacheImplementationMethod.invoke(ext) ?: return@let DEFAULT_CACHE_IMPLEMENTATION_DEFAULT_VALUE + + val optionNameMethod = enumValue::class.java.methods.firstOrNull { it.name == "getOptionName" && it.parameterCount == 0 } + ?: return@let DEFAULT_CACHE_IMPLEMENTATION_DEFAULT_VALUE + + optionNameMethod.invoke(enumValue) as? String + } ?: DEFAULT_CACHE_IMPLEMENTATION_DEFAULT_VALUE + + return AndroidExtensionsGradleModelImpl(androidExtensionsPlugin != null, isExperimental, defaultCacheImplementation) } } @@ -113,6 +135,7 @@ class AndroidExtensionsGradleImportHandler : GradleProjectImportHandler { if (moduleNode.hasAndroidExtensionsPlugin) { newPluginOptions += makePluginOption(EXPERIMENTAL_OPTION.name, moduleNode.isExperimental.toString()) newPluginOptions += makePluginOption(ENABLED_OPTION.name, moduleNode.hasAndroidExtensionsPlugin.toString()) + newPluginOptions += makePluginOption(DEFAULT_CACHE_IMPL_OPTION.name, moduleNode.defaultCacheImplementation) } commonArguments.pluginOptions = newPluginOptions.toTypedArray() diff --git a/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/ExperimentalUtils.kt b/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/ExperimentalUtils.kt index 3377ea64f32..a43bc9cd91e 100644 --- a/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/ExperimentalUtils.kt +++ b/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/ExperimentalUtils.kt @@ -16,20 +16,25 @@ package org.jetbrains.kotlin.android.synthetic.idea +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.module.Module +import kotlinx.android.extensions.CacheImplementation +import org.jetbrains.android.facet.AndroidFacet import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.android.synthetic.AndroidCommandLineProcessor.Companion.ANDROID_COMPILER_PLUGIN_ID import org.jetbrains.kotlin.android.synthetic.AndroidCommandLineProcessor.Companion.EXPERIMENTAL_OPTION import org.jetbrains.kotlin.android.synthetic.AndroidCommandLineProcessor.Companion.ENABLED_OPTION +import org.jetbrains.kotlin.android.synthetic.AndroidCommandLineProcessor.Companion.DEFAULT_CACHE_IMPL_OPTION +import org.jetbrains.kotlin.android.synthetic.AndroidComponentRegistrar.Companion.parseCacheImplementationType import org.jetbrains.kotlin.compiler.plugin.CliOption import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo import org.jetbrains.kotlin.idea.facet.KotlinFacet private val ANNOTATION_OPTION_PREFIX = "plugin:$ANDROID_COMPILER_PLUGIN_ID:" -private fun Module.isOptionEnabledInFacet(option: CliOption): Boolean { - val kotlinFacet = KotlinFacet.get(this) ?: return false - val commonArgs = kotlinFacet.configuration.settings.compilerArguments ?: return false +private fun Module.getOptionValueInFacet(option: CliOption): String? { + val kotlinFacet = KotlinFacet.get(this) ?: return null + val commonArgs = kotlinFacet.configuration.settings.compilerArguments ?: return null val prefix = ANNOTATION_OPTION_PREFIX + option.name + "=" @@ -37,14 +42,25 @@ private fun Module.isOptionEnabledInFacet(option: CliOption): Boolean { ?.firstOrNull { it.startsWith(prefix) } ?.substring(prefix.length) - return optionValue == "true" + return optionValue +} + +private fun isTestMode(module: Module): Boolean { + return ApplicationManager.getApplication().isUnitTestMode && AndroidFacet.getInstance(module) != null } internal val Module.androidExtensionsIsEnabled: Boolean - get() = isOptionEnabledInFacet(ENABLED_OPTION) + get() = isTestMode(this) || getOptionValueInFacet(ENABLED_OPTION) == "true" internal val ModuleInfo.androidExtensionsIsExperimental: Boolean get() { val module = (this as? ModuleSourceInfo)?.module ?: return false - return module.isOptionEnabledInFacet(EXPERIMENTAL_OPTION) + if (isTestMode(module)) return true + return module.getOptionValueInFacet(EXPERIMENTAL_OPTION) == "true" + } + +val ModuleInfo.androidExtensionsGlobalCacheImpl: CacheImplementation + get() { + val module = (this as? ModuleSourceInfo)?.module ?: return CacheImplementation.NO_CACHE + return parseCacheImplementationType(module.getOptionValueInFacet(DEFAULT_CACHE_IMPL_OPTION)) } \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/IDEAndroidExtensionsExpressionCodegenExtension.kt b/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/IDEAndroidExtensionsExpressionCodegenExtension.kt index 0cd53e38a58..5dd3e57bf1a 100644 --- a/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/IDEAndroidExtensionsExpressionCodegenExtension.kt +++ b/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/IDEAndroidExtensionsExpressionCodegenExtension.kt @@ -16,13 +16,15 @@ package org.jetbrains.kotlin.android.synthetic.idea +import kotlinx.android.extensions.CacheImplementation import org.jetbrains.kotlin.android.synthetic.codegen.AbstractAndroidExtensionsExpressionCodegenExtension import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo -import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtElement class IDEAndroidExtensionsExpressionCodegenExtension : AbstractAndroidExtensionsExpressionCodegenExtension() { - override fun isExperimental(clazz: KtClassOrObject): Boolean { - val moduleInfo = clazz.getModuleInfo() ?: return false - return moduleInfo.androidExtensionsIsExperimental - } + override fun isExperimental(element: KtElement?) = + element?.getModuleInfo()?.androidExtensionsIsExperimental ?: false + + override fun getGlobalCacheImpl(element: KtElement?) = + element?.getModuleInfo()?.androidExtensionsGlobalCacheImpl ?: CacheImplementation.DEFAULT } \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/IDEAndroidOnDestroyClassBuilderInterceptorExtension.kt b/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/IDEAndroidOnDestroyClassBuilderInterceptorExtension.kt new file mode 100644 index 00000000000..b0399a8d3b7 --- /dev/null +++ b/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/IDEAndroidOnDestroyClassBuilderInterceptorExtension.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2017 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.idea + +import kotlinx.android.extensions.CacheImplementation +import org.jetbrains.kotlin.android.synthetic.codegen.AbstractAndroidOnDestroyClassBuilderInterceptorExtension +import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo +import org.jetbrains.kotlin.psi.KtElement + +class IDEAndroidOnDestroyClassBuilderInterceptorExtension : AbstractAndroidOnDestroyClassBuilderInterceptorExtension() { + override fun getGlobalCacheImpl(element: KtElement) = element.getModuleInfo().androidExtensionsGlobalCacheImpl +} \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/extensions/CacheImplementation.kt b/plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/extensions/CacheImplementation.kt index 57eafe10ff7..6ecb9b6f3b3 100644 --- a/plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/extensions/CacheImplementation.kt +++ b/plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/extensions/CacheImplementation.kt @@ -23,4 +23,8 @@ public enum class CacheImplementation { public val hasCache: Boolean get() = this != NO_CACHE + + companion object { + val DEFAULT = HASH_MAP + } } \ No newline at end of file diff --git a/plugins/plugins-tests/plugins-tests.iml b/plugins/plugins-tests/plugins-tests.iml index 30adc2f1856..29cfa62c84b 100755 --- a/plugins/plugins-tests/plugins-tests.iml +++ b/plugins/plugins-tests/plugins-tests.iml @@ -42,5 +42,6 @@ + \ No newline at end of file diff --git a/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/synthetic/test/CompilerTestUtils.kt b/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/synthetic/test/CompilerTestUtils.kt index 5515bbd98b8..a633dea5f0a 100755 --- a/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/synthetic/test/CompilerTestUtils.kt +++ b/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/synthetic/test/CompilerTestUtils.kt @@ -17,10 +17,11 @@ package org.jetbrains.kotlin.android.synthetic.test import com.intellij.testFramework.registerServiceInstance +import kotlinx.android.extensions.CacheImplementation import org.jetbrains.kotlin.android.synthetic.AndroidConfigurationKeys import org.jetbrains.kotlin.android.synthetic.AndroidExtensionPropertiesComponentContainerContributor -import org.jetbrains.kotlin.android.synthetic.codegen.AndroidOnDestroyClassBuilderInterceptorExtension import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidExtensionsExpressionCodegenExtension +import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidOnDestroyClassBuilderInterceptorExtension import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager import org.jetbrains.kotlin.android.synthetic.res.AndroidVariant import org.jetbrains.kotlin.android.synthetic.res.CliAndroidLayoutXmlFileManager @@ -46,9 +47,9 @@ fun KtUsefulTestCase.createTestEnvironment(configuration: CompilerConfiguration, val variants = listOf(AndroidVariant.createMainVariant(resDirectories)) project.registerServiceInstance(AndroidLayoutXmlFileManager::class.java, CliAndroidLayoutXmlFileManager(project, "test", variants)) - ExpressionCodegenExtension.registerExtension(project, CliAndroidExtensionsExpressionCodegenExtension(true)) + ExpressionCodegenExtension.registerExtension(project, CliAndroidExtensionsExpressionCodegenExtension(true, CacheImplementation.DEFAULT)) StorageComponentContainerContributor.registerExtension(project, AndroidExtensionPropertiesComponentContainerContributor()) - ClassBuilderInterceptorExtension.registerExtension(project, AndroidOnDestroyClassBuilderInterceptorExtension()) + ClassBuilderInterceptorExtension.registerExtension(project, CliAndroidOnDestroyClassBuilderInterceptorExtension(CacheImplementation.DEFAULT)) PackageFragmentProviderExtension.registerExtension(project, CliAndroidPackageFragmentProviderExtension(true)) addAndroidExtensionsRuntimeLibrary(myEnvironment)