diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidExpressionCodegenExtension.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidExpressionCodegenExtension.kt index 2071ac25ffe..cabf2017b2f 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidExpressionCodegenExtension.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidExpressionCodegenExtension.kt @@ -44,7 +44,7 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { companion object { - private val PROPERTY_NAME = "_\$_findViewCache" + val PROPERTY_NAME = "_\$_findViewCache" val CACHED_FIND_VIEW_BY_ID_METHOD_NAME = "_\$_findCachedViewById" val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache" val ON_DESTROY_METHOD_NAME = "onDestroyView" @@ -57,7 +57,7 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { private class SyntheticPartsGenerateContext( val classBuilder: ClassBuilder, val state: GenerationState, - val descriptor: ClassDescriptor, + val container: ClassDescriptor, val classOrObject: KtClassOrObject, val containerOptions: ContainerOptionsProxy) @@ -135,6 +135,7 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { val context = SyntheticPartsGenerateContext(classBuilder, codegen.state, container, targetClass, containerOptions) context.generateCachedFindViewByIdFunction() context.generateClearCacheFunction() + context.generateCacheField() if (containerOptions.classType.isFragment) { val classMembers = container.unsubstitutedMemberScope.getContributedDescriptors() @@ -143,8 +144,6 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { context.generateOnDestroyFunctionForFragment() } } - - classBuilder.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, PROPERTY_NAME, "Ljava/util/HashMap;", null, null) } private fun FunctionDescriptor.isOnDestroyFunction(): Boolean { @@ -162,10 +161,10 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { methodVisitor.visitCode() val iv = InstructionAdapter(methodVisitor) - val classType = state.typeMapper.mapClass(descriptor) + val classType = state.typeMapper.mapClass(container) iv.load(0, classType) - iv.invokespecial(state.typeMapper.mapClass(descriptor.getSuperClassOrAny()).internalName, ON_DESTROY_METHOD_NAME, "()V", false) + iv.invokespecial(state.typeMapper.mapClass(container.getSuperClassOrAny()).internalName, ON_DESTROY_METHOD_NAME, "()V", false) iv.areturn(Type.VOID_TYPE) FunctionCodegen.endVisit(methodVisitor, ON_DESTROY_METHOD_NAME, classOrObject) @@ -176,29 +175,28 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { methodVisitor.visitCode() val iv = InstructionAdapter(methodVisitor) - val classType = state.typeMapper.mapClass(descriptor) - val className = classType.internalName + val containerType = state.typeMapper.mapClass(container) + val cacheImpl = CacheMechanism.get(containerOptions, iv, containerType) - fun loadCache() { - iv.load(0, classType) - iv.getfield(className, PROPERTY_NAME, "Ljava/util/HashMap;") - } - - loadCache() + cacheImpl.loadCache() val lCacheIsNull = Label() iv.ifnull(lCacheIsNull) - loadCache() - iv.invokevirtual("java/util/HashMap", "clear", "()V", false) + cacheImpl.loadCache() + cacheImpl.clearCache() iv.visitLabel(lCacheIsNull) iv.areturn(Type.VOID_TYPE) FunctionCodegen.endVisit(methodVisitor, CLEAR_CACHE_METHOD_NAME, classOrObject) } + private fun SyntheticPartsGenerateContext.generateCacheField() { + val cacheImpl = CacheMechanism.getType(containerOptions) + classBuilder.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, PROPERTY_NAME, cacheImpl.descriptor, null, null) + } + private fun SyntheticPartsGenerateContext.generateCachedFindViewByIdFunction() { - val classType = state.typeMapper.mapClass(descriptor) - val className = classType.internalName + val containerType = state.typeMapper.mapClass(container) val viewType = Type.getObjectType("android/view/View") @@ -207,32 +205,24 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { methodVisitor.visitCode() val iv = InstructionAdapter(methodVisitor) - fun loadCache() { - iv.load(0, classType) - iv.getfield(className, PROPERTY_NAME, "Ljava/util/HashMap;") - } + val cacheImpl = CacheMechanism.get(containerOptions, iv, containerType) fun loadId() = iv.load(1, Type.INT_TYPE) // Get cache property - loadCache() + cacheImpl.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", "", "()V", false) - iv.putfield(className, PROPERTY_NAME, "Ljava/util/HashMap;") + cacheImpl.initCache() // Get View from cache iv.visitLabel(lCacheNonNull) - loadCache() + cacheImpl.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) + cacheImpl.getViewFromCache() iv.checkcast(viewType) iv.store(2, viewType) @@ -241,7 +231,7 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { iv.ifnonnull(lViewNonNull) // Resolve View via findViewById if not in cache - iv.load(0, classType) + iv.load(0, containerType) when (containerOptions.classType) { AndroidContainerType.ACTIVITY, AndroidContainerType.SUPPORT_FRAGMENT_ACTIVITY, AndroidContainerType.VIEW, AndroidContainerType.DIALOG -> { loadId() @@ -252,7 +242,7 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { val targetClassName: String if (containerOptions.classType == AndroidContainerType.LAYOUT_CONTAINER) { methodName = "getEntityView" - targetClassName = classType.internalName + targetClassName = containerType.internalName } else { methodName = "getView" targetClassName = containerOptions.classType.internalClassName @@ -277,12 +267,9 @@ class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { iv.store(2, viewType) // Store resolved View in cache - loadCache() + cacheImpl.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() + cacheImpl.putViewToCache { iv.load(2, viewType) } iv.visitLabel(lViewNonNull) iv.load(2, viewType) 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 new file mode 100644 index 00000000000..90f69117f6a --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/CacheMechanisms.kt @@ -0,0 +1,122 @@ +/* + * 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.android.synthetic.descriptors.ContainerOptionsProxy +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter + +interface CacheMechanism { + /** Push the cache object onto the stack. */ + fun loadCache() + + /** Init cache field. */ + fun initCache() + + /** Clear cache. The cache storage should be on the stack. */ + fun clearCache() + + /** Push the cached view onto the stack, or push `null` if the view is not cached. `Int` id should be on the stack. */ + fun getViewFromCache() + + /** Cache the view. `Int` id should be on the stack. */ + fun putViewToCache(getView: () -> Unit) + + companion object { + fun getType(containerOptions: ContainerOptionsProxy): Type { + return Type.getObjectType(when (containerOptions.cache) { + 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) { + CacheImplementation.HASH_MAP -> HashMapCacheMechanism(iv, containerType) + CacheImplementation.SPARSE_ARRAY -> SparseArrayCacheMechanism(iv, containerType) + CacheImplementation.NO_CACHE -> throw IllegalArgumentException("Container should support cache") + } + } + } +} + +internal class HashMapCacheMechanism( + val iv: InstructionAdapter, + val containerType: Type +) : CacheMechanism { + override fun loadCache() { + iv.load(0, containerType) + iv.getfield(containerType.internalName, AndroidExpressionCodegenExtension.PROPERTY_NAME, "Ljava/util/HashMap;") + } + + override fun initCache() { + iv.load(0, containerType) + iv.anew(Type.getType("Ljava/util/HashMap;")) + iv.dup() + iv.invokespecial("java/util/HashMap", "", "()V", false) + iv.putfield(containerType.internalName, AndroidExpressionCodegenExtension.PROPERTY_NAME, "Ljava/util/HashMap;") + } + + override fun clearCache() { + iv.invokevirtual("java/util/HashMap", "clear", "()V", false) + } + + override fun getViewFromCache() { + iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false) + iv.invokevirtual("java/util/HashMap", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", false) + } + + override fun putViewToCache(getView: () -> Unit) { + iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false) + getView() + iv.invokevirtual("java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false) + iv.pop() + } +} + +internal class SparseArrayCacheMechanism( + val iv: InstructionAdapter, + val containerType: Type +) : CacheMechanism { + override fun loadCache() { + iv.load(0, containerType) + iv.getfield(containerType.internalName, AndroidExpressionCodegenExtension.PROPERTY_NAME, "Landroid/util/SparseArray;") + } + + override fun initCache() { + iv.load(0, containerType) + iv.anew(Type.getType("Landroid/util/SparseArray;")) + iv.dup() + iv.invokespecial("android/util/SparseArray", "", "()V", false) + iv.putfield(containerType.internalName, AndroidExpressionCodegenExtension.PROPERTY_NAME, "Landroid/util/SparseArray;") + } + + override fun clearCache() { + iv.invokevirtual("android/util/SparseArray", "clear", "()V", false) + } + + override fun getViewFromCache() { + iv.invokevirtual("android/util/SparseArray", "get", "(I)Ljava/lang/Object;", false) + } + + override fun putViewToCache(getView: () -> Unit) { + getView() + iv.invokevirtual("android/util/SparseArray", "put", "(ILjava/lang/Object;)V", false) + } +} \ No newline at end of file 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 7ebb616c8b4..efdd8c5115b 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 @@ -33,7 +33,7 @@ class ContainerOptionsProxy(val classType: AndroidContainerType, val cache: Cach private val CONTAINER_OPTIONS_FQNAME = FqName(ContainerOptions::class.java.canonicalName) private val CACHE_NAME = ContainerOptions::cache.name - private val DEFAULT_CACHE_IMPL = HASH_MAP + private val DEFAULT_CACHE_IMPL = SPARSE_ARRAY fun get(container: ClassDescriptor): ContainerOptionsProxy { if (container.kind != ClassKind.CLASS) { @@ -52,7 +52,7 @@ class ContainerOptionsProxy(val classType: AndroidContainerType, val cache: Cach if (supportsCache) DEFAULT_CACHE_IMPL else NO_CACHE) } - val cache = anno.getEnumValue(CACHE_NAME, HASH_MAP) { valueOf(it) } + val cache = anno.getEnumValue(CACHE_NAME, DEFAULT_CACHE_IMPL) { valueOf(it) } return ContainerOptionsProxy(classType, cache) } diff --git a/plugins/android-extensions/android-extensions-compiler/testData/codegen/android/FakeSparseArray.kt b/plugins/android-extensions/android-extensions-compiler/testData/codegen/android/FakeSparseArray.kt new file mode 100644 index 00000000000..0fec8cb8227 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/testData/codegen/android/FakeSparseArray.kt @@ -0,0 +1,19 @@ +package android.util + +class SparseArray { + private val map = HashMap() + + fun get(key: Int): E? { + return map.get(key) + } + + fun put(key: Int, value: E) { + map.put(key, value) + } + + fun remove(key: Int): E? { + return map.remove(key) + } + + fun clear() {} +} \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-compiler/testData/codegen/bytecodeShape/simple/simple.kt b/plugins/android-extensions/android-extensions-compiler/testData/codegen/bytecodeShape/simple/simple.kt index ebf813e717f..1144dfadd9e 100644 --- a/plugins/android-extensions/android-extensions-compiler/testData/codegen/bytecodeShape/simple/simple.kt +++ b/plugins/android-extensions/android-extensions-compiler/testData/codegen/bytecodeShape/simple/simple.kt @@ -13,4 +13,9 @@ public class MyActivity : Activity() { // 1 public _\$_clearFindViewByIdCache // 1 GETSTATIC test/R\$id\.login // 1 INVOKEVIRTUAL test/MyActivity\._\$_findCachedViewById -// 1 CHECKCAST android/widget/Button \ No newline at end of file +// 1 CHECKCAST android/widget/Button +// 12 android/util/SparseArray +// 1 INVOKEVIRTUAL android/util/SparseArray\.get +// 1 INVOKEVIRTUAL android/util/SparseArray\.put +// 1 INVOKEVIRTUAL android/util/SparseArray\.clear +// 0 java/util/HashMap \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-compiler/testData/codegen/bytecodeShape/simpleHashMapCacheImplementation/res/layout/layout.xml b/plugins/android-extensions/android-extensions-compiler/testData/codegen/bytecodeShape/simpleHashMapCacheImplementation/res/layout/layout.xml new file mode 100644 index 00000000000..4d73173b521 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/testData/codegen/bytecodeShape/simpleHashMapCacheImplementation/res/layout/layout.xml @@ -0,0 +1,17 @@ + + + + +