serialization: kotlin data class full support (initial, enabled only in tests for now) — part 2

This commit is contained in:
Vladimir Krivosheev
2017-02-10 06:44:07 +01:00
parent 123e339952
commit 6496cdb440
15 changed files with 151 additions and 118 deletions
-1
View File
@@ -112,7 +112,6 @@
<orderEntry type="module" module-name="typeMigration" />
<orderEntry type="module" module-name="platform-tests" />
<orderEntry type="module" module-name="android" />
<orderEntry type="module" module-name="configuration-store-tests" scope="TEST" />
<orderEntry type="module" module-name="devkit-tests" scope="TEST" />
<orderEntry type="module" module-name="diff-tests" scope="TEST" />
<orderEntry type="module" module-name="built-in-server-tests" scope="TEST" />
-1
View File
@@ -55,7 +55,6 @@
<orderEntry type="module" module-name="built-in-server-api" scope="TEST" />
<orderEntry type="module" module-name="configuration-store-impl" scope="TEST" />
<orderEntry type="library" scope="TEST" name="memoryfilesystem" level="project" />
<orderEntry type="module" module-name="configuration-store-tests" scope="TEST" />
<orderEntry type="module" module-name="testExtensions" scope="TEST" />
</component>
</module>
@@ -4,6 +4,7 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="com.intellij.configurationStore" />
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" packagePrefix="com.intellij.configurationStore" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
@@ -16,5 +17,8 @@
<orderEntry type="module" module-name="bootstrap" />
<orderEntry type="library" name="xpp3-1.1.4-min" level="project" />
<orderEntry type="module" module-name="vcs-api" />
<orderEntry type="library" scope="TEST" name="assertJ" level="project" />
<orderEntry type="module" module-name="testExtensions" scope="TEST" />
<orderEntry type="module" module-name="testFramework" scope="TEST" />
</component>
</module>
@@ -33,7 +33,6 @@ import com.intellij.util.lang.CompoundRuntimeException
import com.intellij.util.loadElement
import com.intellij.util.toByteArray
import com.intellij.util.xmlb.annotations.Tag
import com.intellij.util.xmlb.serialize
import gnu.trove.THashMap
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
@@ -15,6 +15,7 @@
*/
package com.intellij.configurationStore
import com.intellij.configurationStore.xml.XmlElementStorageTest
import org.junit.runner.RunWith
import org.junit.runners.Suite
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.xmlb
package com.intellij.configurationStore.xml
import com.intellij.configurationStore.deserialize
import com.intellij.util.loadElement
@@ -13,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.configurationStore
package com.intellij.configurationStore.xml
import com.intellij.configurationStore.StateMap
import com.intellij.configurationStore.XmlElementStorage
import com.intellij.openapi.util.JDOMBuilder.attr
import com.intellij.openapi.util.JDOMBuilder.tag
import org.assertj.core.api.Assertions.assertThat
@@ -52,7 +54,7 @@ class XmlElementStorageTest {
override fun loadLocalData() = myElement
override fun createSaveSession(states: StateMap) = object : XmlElementStorage.XmlElementStorageSaveSession<MyXmlElementStorage>(states, this) {
override fun createSaveSession(states: StateMap) = object : XmlElementStorageSaveSession<MyXmlElementStorage>(states, this) {
override fun saveLocally(element: Element?) {
savedElement = element?.clone()
}
@@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.xmlb
package com.intellij.configurationStore.xml
import com.intellij.util.xmlb.SkipDefaultsSerializationFilter
import com.intellij.util.xmlb.annotations.MapAnnotation
import com.intellij.util.xmlb.annotations.Property
import com.intellij.util.xmlb.annotations.Tag
@@ -13,24 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.xmlb
package com.intellij.configurationStore.xml
import com.intellij.configurationStore.deserialize
import com.intellij.configurationStore.serialize
import com.intellij.openapi.util.JDOMExternalizableStringList
import com.intellij.openapi.util.JDOMUtil
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.text.StringUtil
import com.intellij.testFramework.assertConcurrent
import com.intellij.util.SmartList
import com.intellij.util.loadElement
import com.intellij.util.xmlb.*
import com.intellij.util.xmlb.annotations.*
import com.intellij.util.xmlb.annotations.AbstractCollection
import junit.framework.AssertionFailedError
import junit.framework.TestCase
import org.assertj.core.api.Assertions.assertThat
import org.intellij.lang.annotations.Language
import org.jdom.Element
import org.junit.Test
import java.util.*
import java.util.concurrent.atomic.AtomicReference
internal open class BeanWithPublicFields(var INT_V: Int = 1, var STRING_V: String? = "hello") : Comparable<BeanWithPublicFields> {
override fun compareTo(other: BeanWithPublicFields) = StringUtil.compare(STRING_V, other.STRING_V, false)
@@ -211,9 +213,9 @@ internal class XmlSerializerTest {
doSerializerTest("<BeanWithSet>\n" + " <option name=\"VALUES\">\n" + " <set>\n" + " <option value=\"1\" />\n" + " <option value=\"2\" />\n" + " <option value=\"3\" />\n" + " </set>\n" + " </option>\n" + "</BeanWithSet>", bean)
}
private data class BeanWithOption(@OptionTag("path") var PATH: String? = null)
@Test fun OptionTag() {
data class BeanWithOption(@OptionTag("path") var PATH: String? = null)
val bean = BeanWithOption()
bean.PATH = "123"
doSerializerTest("<BeanWithOption>\n" + " <option name=\"path\" value=\"123\" />\n" + "</BeanWithOption>", bean)
@@ -238,34 +240,33 @@ internal class XmlSerializerTest {
}
private class BeanWithFieldWithTagAnnotation {
@Tag("name") var STRING_V: String = "hello"
@Tag("name") var STRING_V = "hello"
}
@Test fun ParallelDeserialization() {
@Test fun `parallel deserialization`() {
val e = Element("root").addContent(Element("name").setText("x"))
XmlSerializer.deserialize<BeanWithArray>(e, BeanWithArray::class.java)//to initialize XmlSerializerImpl.ourBindings
val exc = AtomicReference<AssertionFailedError>()
val threads = Array(5) {
Thread(Runnable {
try {
for (j in 0..9) {
val bean = e.deserialize<BeanWithFieldWithTagAnnotation>()
assertThat(bean).isNotNull()
assertThat(bean.STRING_V).isEqualTo("x")
}
assertConcurrent(*Array(5) {
{
for (i in 0..9) {
val bean = e.deserialize<BeanWithFieldWithTagAnnotation>()
assertThat(bean).isNotNull()
assertThat(bean.STRING_V).isEqualTo("x")
}
catch (e: AssertionFailedError) {
exc.set(e)
}
})
}
@Test fun `parallel deserialization 2`() {
val e = Element("root").addContent(Element("name").setText("x"))
assertConcurrent(*Array(5) {
{
for (i in 0..9) {
val bean = e.deserialize<BeanWithFieldWithTagAnnotation>()
assertThat(bean).isNotNull()
assertThat(bean.STRING_V).isEqualTo("x")
}
}, "XmlSerializerTest#testParallelDeserialization-$it")
}
for (thread in threads) {
thread.start()
}
for (thread in threads) {
thread.join()
}
exc.get()?.let { throw it }
}
})
}
@Test fun FieldWithTagAnnotation() {
@@ -594,7 +595,7 @@ internal class XmlSerializerTest {
private class BeanWithJDOMElement {
var STRING_V: String = "hello"
@Tag("actions") var actions: org.jdom.Element? = null
@Tag("actions") var actions: Element? = null
}
@Test fun SerializeJDOMElementField() {
@@ -608,7 +609,8 @@ internal class XmlSerializerTest {
}
@Test fun DeserializeJDOMElementField() {
val bean = XmlSerializer.deserialize<BeanWithJDOMElement>(JDOMUtil.loadDocument("<BeanWithJDOMElement><option name=\"STRING_V\" value=\"bye\"/><actions><action/><action/></actions></BeanWithJDOMElement>").rootElement, BeanWithJDOMElement::class.java)
val bean = XmlSerializer.deserialize<BeanWithJDOMElement>(
JDOMUtil.loadDocument("<BeanWithJDOMElement><option name=\"STRING_V\" value=\"bye\"/><actions><action/><action/></actions></BeanWithJDOMElement>").rootElement, BeanWithJDOMElement::class.java)
TestCase.assertEquals("bye", bean.STRING_V)
TestCase.assertNotNull(bean.actions)
@@ -617,7 +619,7 @@ internal class XmlSerializerTest {
class BeanWithJDOMElementArray {
var STRING_V: String = "hello"
@Tag("actions") var actions: Array<org.jdom.Element>? = null
@Tag("actions") var actions: Array<Element>? = null
}
@Test fun JDOMElementArrayField() {
@@ -840,7 +842,8 @@ internal class XmlSerializerTest {
val value = Element("value")
list.writeExternal(value)
val o = XmlSerializer.deserialize<Bean4>(Element("state").addContent(Element("option").setAttribute("name", "myList").addContent(value)), Bean4::class.java)!!
val o = XmlSerializer.deserialize<Bean4>(
Element("state").addContent(Element("option").setAttribute("name", "myList").addContent(value)), Bean4::class.java)!!
assertSerializer(o, "<b>\n" + " <list>\n" + " <item value=\"one\" />\n" + " <item value=\"two\" />\n" + " <item value=\"three\" />\n" + " </list>\n" + "</b>", SkipDefaultsSerializationFilter())
}
@@ -854,14 +857,15 @@ internal class XmlSerializerTest {
<h4>Node.js integration</h4>
]]>
</description>
</bean>""".reader()), Bean::class.java)!!
</bean>""".reader()), Bean::class.java)
assertThat(bean.description).isEqualToIgnoringWhitespace("<h4>Node.js integration</h4>")
bean = XmlSerializer.deserialize(loadElement("""<bean><description><![CDATA[<h4>Node.js integration</h4>]]></description></bean>""".reader()), Bean::class.java)!!
bean = XmlSerializer.deserialize(
loadElement("""<bean><description><![CDATA[<h4>Node.js integration</h4>]]></description></bean>""".reader()), Bean::class.java)
assertThat(bean.description).isEqualTo("<h4>Node.js integration</h4>")
}
private fun checkSmartSerialization(bean: XmlSerializerTest.Bean2, serialized: String) {
private fun checkSmartSerialization(bean: Bean2, serialized: String) {
val serializer = SmartSerializer()
serializer.readExternal(bean, JDOMUtil.loadDocument(serialized).rootElement)
val serializedState = Element("Bean2")
@@ -888,11 +892,7 @@ internal fun <T: Any> doSerializerTest(@Language("XML") expectedText: String, be
val element = assertSerializer(bean, expectedTrimmed, filter)
//test deserializer
val o = XmlSerializer.deserialize(element, bean.javaClass)!!
val o = XmlSerializer.deserialize(element, bean.javaClass)
assertSerializer(o, expectedTrimmed, filter, "Deserialization failure")
return o
}
fun <T : Any> T.serialize(filter: SerializationFilter? = SkipDefaultValuesSerializationFilters()): Element = XmlSerializer.serialize(this, filter)
inline fun <reified T: Any> Element.deserialize(): T = XmlSerializer.deserialize(this, T::class.java)!!
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-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.
@@ -15,14 +15,10 @@
*/
package org.jetbrains.concurrency
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.lang.CompoundRuntimeException
import com.intellij.testFramework.assertConcurrent
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicInteger
@@ -115,40 +111,4 @@ class AsyncPromiseTest {
r()
assertThat(count.get()).isEqualTo(numThreads + 1)
}
}
fun assertConcurrent(vararg runnables: () -> Any?, maxTimeoutSeconds: Int = 5) {
val numThreads = runnables.size
val exceptions = ContainerUtil.createLockFreeCopyOnWriteList<Throwable>()
val threadPool = Executors.newFixedThreadPool(numThreads)
try {
val allExecutorThreadsReady = CountDownLatch(numThreads)
val afterInitBlocker = CountDownLatch(1)
val allDone = CountDownLatch(numThreads)
for (submittedTestRunnable in runnables) {
threadPool.submit {
allExecutorThreadsReady.countDown()
try {
afterInitBlocker.await()
submittedTestRunnable()
}
catch (e: Throwable) {
exceptions.add(e)
}
finally {
allDone.countDown()
}
}
}
// wait until all threads are ready
assertThat(allExecutorThreadsReady.await((runnables.size * 1000).toLong(), TimeUnit.MILLISECONDS)).isTrue()
// start all test runners
afterInitBlocker.countDown()
assertThat(allDone.await(maxTimeoutSeconds.toLong(), TimeUnit.SECONDS)).isTrue()
}
finally {
threadPool.shutdownNow()
}
CompoundRuntimeException.throwIfNotEmpty(exceptions)
}
@@ -16,18 +16,22 @@
package com.intellij.configurationStore
import com.intellij.openapi.util.JDOMUtil
import com.intellij.openapi.util.Pair
import com.intellij.reference.SoftReference
import com.intellij.util.xmlb.*
import gnu.trove.THashMap
import org.jdom.Element
import org.jdom.JDOMException
import java.io.IOException
import java.lang.reflect.Type
import java.net.URL
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.reflect.primaryConstructor
private var bindingCache: SoftReference<MutableMap<Pair<Type, MutableAccessor>, Binding>>? = null
fun <T : Any> T.serialize(filter: SerializationFilter? = SkipDefaultValuesSerializationFilters()): Element = XmlSerializer.serialize(this, filter)
inline fun <reified T: Any> Element.deserialize(): T = deserialize(this, T::class.java)
fun <T> deserialize(element: Element, aClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
@@ -56,12 +60,29 @@ fun <T> deserialize(url: URL, aClass: Class<T>): T {
}
}
private var _bindingCache: SoftReference<MutableMap<BindingCacheKey, Binding>>? = null
private val bindingCache: MutableMap<BindingCacheKey, Binding>
get() {
var map = _bindingCache?.get()
if (map == null) {
map = THashMap()
_bindingCache = SoftReference(map)
}
return map
}
private val cacheLock = ReentrantReadWriteLock()
private fun <T> getBinding(aClass: Class<T>, originalType: Type = aClass, accessor: MutableAccessor? = null): Binding {
val key = Pair.create<Type, MutableAccessor>(originalType, accessor)
val map = getBindingCacheMap()
var binding: Binding? = map.get(key)
if (binding == null) {
binding = XmlSerializerImpl.getNonCachedClassBinding(aClass, accessor, originalType) ?: KotlinAwareBeanBinding(aClass, accessor)
val key = BindingCacheKey(originalType, accessor)
val map = bindingCache
return cacheLock.read { map.get(key) } ?: cacheLock.write {
map.get(key)?.let {
return it
}
val binding = XmlSerializerImpl.createClassBinding(aClass, accessor, originalType) ?: KotlinAwareBeanBinding(aClass, accessor)
map.put(key, binding)
try {
binding.init(originalType)
@@ -70,19 +91,11 @@ private fun <T> getBinding(aClass: Class<T>, originalType: Type = aClass, access
map.remove(key)
throw e
}
binding
}
return binding
}
private fun getBindingCacheMap(): MutableMap<Pair<Type, MutableAccessor>, Binding> {
var map = SoftReference.dereference<MutableMap<Pair<Type, MutableAccessor>, Binding>>(bindingCache)
if (map == null) {
map = ConcurrentHashMap<Pair<Type, MutableAccessor>, Binding>()
bindingCache = SoftReference<MutableMap<Pair<Type, MutableAccessor>, Binding>>(map)
}
return map
}
private data class BindingCacheKey(val type: Type, val accessor: MutableAccessor?)
private class KotlinAwareBeanBinding(beanClass: Class<*>, accessor: MutableAccessor? = null) : BeanBinding(beanClass, accessor) {
override fun deserialize(context: Any?, element: Element): Any {
@@ -0,0 +1,60 @@
/*
* Copyright 2000-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 com.intellij.testFramework
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.lang.CompoundRuntimeException
import org.assertj.core.api.Assertions.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
fun assertConcurrent(vararg runnables: () -> Any?, maxTimeoutSeconds: Int = 5) {
val numThreads = runnables.size
val exceptions = ContainerUtil.createLockFreeCopyOnWriteList<Throwable>()
val threadPool = Executors.newFixedThreadPool(numThreads)
try {
val allExecutorThreadsReady = CountDownLatch(numThreads)
val afterInitBlocker = CountDownLatch(1)
val allDone = CountDownLatch(numThreads)
for (submittedTestRunnable in runnables) {
threadPool.submit {
allExecutorThreadsReady.countDown()
try {
afterInitBlocker.await()
submittedTestRunnable()
}
catch (e: Throwable) {
exceptions.add(e)
}
finally {
allDone.countDown()
}
}
}
// wait until all threads are ready
assertThat(allExecutorThreadsReady.await((runnables.size * 1000).toLong(), TimeUnit.MILLISECONDS)).isTrue()
// start all test runners
afterInitBlocker.countDown()
assertThat(allDone.await(maxTimeoutSeconds.toLong(), TimeUnit.SECONDS)).isTrue()
}
finally {
threadPool.shutdownNow()
}
CompoundRuntimeException.throwIfNotEmpty(exceptions)
}
@@ -17,6 +17,7 @@ package com.intellij.util.xmlb;
import com.intellij.openapi.util.JDOMExternalizableStringList;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xmlb.annotations.CollectionBean;
import org.jdom.Content;
import org.jdom.Element;
@@ -34,7 +35,6 @@ import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class XmlSerializerImpl {
private static Reference<Map<Pair<Type, MutableAccessor>, Binding>> ourBindings;
@@ -118,7 +118,7 @@ public class XmlSerializerImpl {
Map<Pair<Type, MutableAccessor>, Binding> map = getBindingCacheMap();
Binding binding = map.get(key);
if (binding == null) {
binding = getNonCachedClassBinding(aClass, accessor, originalType);
binding = createClassBinding(aClass, accessor, originalType);
if (binding == null) {
binding = new BeanBinding(aClass, accessor);
}
@@ -139,14 +139,14 @@ public class XmlSerializerImpl {
private static Map<Pair<Type, MutableAccessor>, Binding> getBindingCacheMap() {
Map<Pair<Type, MutableAccessor>, Binding> map = com.intellij.reference.SoftReference.dereference(ourBindings);
if (map == null) {
map = new ConcurrentHashMap<Pair<Type, MutableAccessor>, Binding>();
map = ContainerUtil.newConcurrentMap();
ourBindings = new SoftReference<Map<Pair<Type, MutableAccessor>, Binding>>(map);
}
return map;
}
@Nullable
public static Binding getNonCachedClassBinding(@NotNull Class<?> aClass, @Nullable MutableAccessor accessor, @NotNull Type originalType) {
public static Binding createClassBinding(@NotNull Class<?> aClass, @Nullable MutableAccessor accessor, @NotNull Type originalType) {
if (aClass.isArray()) {
if (Element.class.isAssignableFrom(aClass.getComponentType())) {
assert accessor != null;
@@ -19,7 +19,6 @@
<orderEntry type="library" name="Slf4j" level="project" />
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
<orderEntry type="module" module-name="configuration-store-impl" />
<orderEntry type="module" module-name="configuration-store-tests" scope="TEST" />
<orderEntry type="module" module-name="util-tests" scope="TEST" />
<orderEntry type="module" module-name="settings-repository" />
<orderEntry type="library" scope="TEST" name="assertJ" level="project" />
@@ -15,13 +15,9 @@
*/
package org.jetbrains.settingsRepository.test
import com.intellij.configurationStore.SchemeManagerImpl
import com.intellij.configurationStore.TestScheme
import com.intellij.configurationStore.TestSchemesProcessor
import com.intellij.configurationStore.save
import com.intellij.configurationStore.*
import com.intellij.testFramework.ProjectRule
import com.intellij.util.toByteArray
import com.intellij.util.xmlb.serialize
import org.assertj.core.api.Assertions.assertThat
import org.eclipse.jgit.lib.Repository
import org.jetbrains.settingsRepository.ReadonlySource