From 3e9153596a5b78b3c8e6d5d8bf3097eebdf3ec5a Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Fri, 17 May 2019 09:00:47 +0200 Subject: [PATCH] =?UTF-8?q?reduce=20memory=20usage=20=E2=80=94=20same=20ma?= =?UTF-8?q?p=20binding=20for=20the=20same=20type=20and=20annotation=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitOrigin-RevId: 695fc10b114200ee67f8e10914318511856f7622 --- platform/object-serializer/src/BeanBinding.kt | 3 ++ platform/object-serializer/src/Binding.kt | 2 + .../object-serializer/src/BindingProducer.kt | 31 +++++++----- .../src/CollectionBinding.kt | 1 + .../src/IonBindingProducer.kt | 48 +++++++------------ platform/object-serializer/testSrc/MapTest.kt | 17 +++++++ 6 files changed, 60 insertions(+), 42 deletions(-) diff --git a/platform/object-serializer/src/BeanBinding.kt b/platform/object-serializer/src/BeanBinding.kt index 24f81de251be..667f56bd283e 100644 --- a/platform/object-serializer/src/BeanBinding.kt +++ b/platform/object-serializer/src/BeanBinding.kt @@ -28,6 +28,9 @@ internal class BeanBinding(beanClass: Class<*>) : BaseBeanBinding(beanClass), Ro computeNonDefaultConstructorInfo(beanClass) } + // type parameters for bean binding doesn't play any role, should be the only binding for such class + override fun createCacheKey(aClass: Class<*>, type: Type) = aClass + override fun init(originalType: Type, context: BindingInitializationContext) { val list = context.propertyCollector.collect(beanClass) properties = list diff --git a/platform/object-serializer/src/Binding.kt b/platform/object-serializer/src/Binding.kt index 6fc14339c5c2..39c21b1092bd 100644 --- a/platform/object-serializer/src/Binding.kt +++ b/platform/object-serializer/src/Binding.kt @@ -22,6 +22,8 @@ interface NestedBinding : RootBinding { * Binding that can read and write data of root object. */ interface RootBinding : Binding { + fun createCacheKey(aClass: Class<*>, type: Type = aClass) = type + fun init(originalType: Type, context: BindingInitializationContext) { } diff --git a/platform/object-serializer/src/BindingProducer.kt b/platform/object-serializer/src/BindingProducer.kt index 868f4f7f32d0..b7b3c2e88c85 100644 --- a/platform/object-serializer/src/BindingProducer.kt +++ b/platform/object-serializer/src/BindingProducer.kt @@ -25,18 +25,27 @@ abstract class BindingProducer : BindingInitializationContext { abstract fun getNestedBinding(accessor: MutableAccessor): NestedBinding fun getRootBinding(aClass: Class<*>, type: Type = aClass): RootBinding { - val cacheKey = createCacheKey(aClass, type) - return cacheLock.read { - // create cache only under write lock - cache.get(cacheKey) - } ?: cacheLock.write { - cache.get(cacheKey)?.let { + fun getByTypeOrByClass(): RootBinding? { + var result = cache.get(type) + if (result == null && aClass !== type) { + result = cache.get(aClass) + } + return result + } + + cacheLock.read { + getByTypeOrByClass()?.let { + return it + } + } + + cacheLock.write { + getByTypeOrByClass()?.let { return it } - createRootBinding(aClass, type, cacheKey) - val binding = createRootBinding(aClass, type, cacheKey) - cache.put(cacheKey, binding) + val binding = createRootBinding(aClass, type) + cache.put(binding.createCacheKey(aClass, type), binding) try { binding.init(type, this) } @@ -48,9 +57,7 @@ abstract class BindingProducer : BindingInitializationContext { } } - protected open fun createCacheKey(aClass: Class<*>, type: Type) = type - - protected abstract fun createRootBinding(aClass: Class<*>, type: Type, cacheKey: Type): RootBinding + protected abstract fun createRootBinding(aClass: Class<*>, type: Type): RootBinding @Suppress("unused") fun clearBindingCache() { diff --git a/platform/object-serializer/src/CollectionBinding.kt b/platform/object-serializer/src/CollectionBinding.kt index 377a513d22cf..2e3228da74f7 100644 --- a/platform/object-serializer/src/CollectionBinding.kt +++ b/platform/object-serializer/src/CollectionBinding.kt @@ -10,6 +10,7 @@ import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.util.function.Consumer +// CollectionBinding implements NestedBinding directly because can mutate property value directly internal abstract class BaseCollectionBinding(itemType: Type, context: BindingInitializationContext) : RootBinding, NestedBinding { private val itemBinding = createElementBindingByType(itemType, context) diff --git a/platform/object-serializer/src/IonBindingProducer.kt b/platform/object-serializer/src/IonBindingProducer.kt index e9362aa0a38d..34d926ab14ff 100644 --- a/platform/object-serializer/src/IonBindingProducer.kt +++ b/platform/object-serializer/src/IonBindingProducer.kt @@ -43,46 +43,39 @@ internal class IonBindingProducer(override val propertyCollector: PropertyCollec } } - // type parameters for bean binding doesn't play any role, should be the only binding for such class - override fun createCacheKey(aClass: Class<*>, type: Type): Type { - when { - aClass !== type && !Collection::class.java.isAssignableFrom(aClass) && !classToRootBindingFactory.contains(aClass) -> return aClass - else -> return type - } - } - - override fun createRootBinding(aClass: Class<*>, type: Type, cacheKey: Type): RootBinding { + override fun createRootBinding(aClass: Class<*>, type: Type): RootBinding { val customFactory = classToRootBindingFactory.get(aClass) if (customFactory != null) { return customFactory.invoke() } return when { - Collection::class.java.isAssignableFrom(aClass) -> CollectionBinding(type as ParameterizedType, this) + Collection::class.java.isAssignableFrom(aClass) -> { + CollectionBinding(type as ParameterizedType, this) + } Map::class.java.isAssignableFrom(aClass) -> { val typeArguments = (type as ParameterizedType).actualTypeArguments MapBinding(typeArguments[0], typeArguments[1], this) } - aClass.isArray -> ArrayBinding(aClass.componentType, this) + aClass.isArray -> { + ArrayBinding(aClass.componentType, this) + } aClass.isEnum -> { @Suppress("UNCHECKED_CAST") EnumBinding(aClass as Class>) } + aClass.isInterface || Modifier.isAbstract(aClass.modifiers) -> { + PolymorphicBinding(aClass) + } + aClass is Proxy -> { + throw SerializationException("$aClass class is not supported") + } + aClass == Class::class.java -> { + // Class can be supported, but it will be implemented only when will be a real use case + throw SerializationException("$aClass class is not supported") + } else -> { - assert(cacheKey === aClass) - if (aClass.isInterface || Modifier.isAbstract(aClass.modifiers)) { - PolymorphicBinding(aClass) - } - else if (aClass is Proxy) { - throw SerializationException("$aClass class is not supported") - } - else if (aClass == Class::class.java) { - // Class can be supported, but it will be implemented only when will be a real use case - throw SerializationException("$aClass class is not supported") - } - else { - BeanBinding(aClass) - } + BeanBinding(aClass) } } } @@ -98,12 +91,7 @@ internal class IonBindingProducer(override val propertyCollector: PropertyCollec return it(accessor) } - // CollectionBinding implements NestedBinding directly because can mutate property value directly return when { - Map::class.java.isAssignableFrom(aClass) -> { - val typeArguments = (type as ParameterizedType).actualTypeArguments - MapBinding(typeArguments[0], typeArguments[1], this) - } aClass.isArray -> ArrayBinding(aClass.componentType, this) java.lang.Number::class.java.isAssignableFrom(aClass) -> PropertyBinding(NumberAsObjectBinding()) aClass.isInterface || Modifier.isAbstract(aClass.modifiers) -> { diff --git a/platform/object-serializer/testSrc/MapTest.kt b/platform/object-serializer/testSrc/MapTest.kt index 15678a78d194..4a6b9e15f4c3 100644 --- a/platform/object-serializer/testSrc/MapTest.kt +++ b/platform/object-serializer/testSrc/MapTest.kt @@ -6,6 +6,7 @@ import gnu.trove.THashMap import org.junit.Rule import org.junit.Test import org.junit.rules.TestName +import java.io.ByteArrayOutputStream import java.util.* class MapTest { @@ -17,6 +18,22 @@ class MapTest { return test(bean, testName, writeConfiguration) } + @Test + fun `same map binding for the same type and annotation set`() { + val serializer = ObjectSerializer() + val bindingProducer = getBindingProducer(serializer) + + @Suppress("unused") + class TestBean { + @JvmField + val a: MutableMap = THashMap() + val b: MutableMap = THashMap() + } + + serializer.write(TestBean(), ByteArrayOutputStream()) + assertThat(getBindingCount(bindingProducer)).isEqualTo(3 /* TestBean/String/Map */) + } + @Test fun map() { val bean = TestMapBean()