BaseState - introduce convenient methods for linkedMap/map instead of generic map
authordevelar <develar@gmail.com>
Sat, 27 Apr 2019 07:44:50 +0000 (09:44 +0200)
committerintellij-monorepo-bot <intellij-monorepo-bot-no-reply@jetbrains.com>
Sun, 28 Apr 2019 16:48:05 +0000 (19:48 +0300)
GitOrigin-RevId: d0d6ac52bd785ab6555f70fdc0f45b41593b3e96

java/execution/impl/src/com/intellij/execution/application/JvmMainMethodRunConfigurationOptions.kt
platform/configuration-store-impl/testSrc/StoredPropertyStateTest.kt
platform/projectModel-api/src/com/intellij/configurationStore/properties/MapStoredProperty.kt
platform/projectModel-api/src/com/intellij/openapi/components/BaseState.kt
plugins/terminal/src/org/jetbrains/plugins/terminal/EnvironmentVariablesDataOptions.kt

index a4b772219a7feac7e4bedb9e5196ced25ad5de89..19c17faf6a4a0014af9c14051b5be69e38f4dd30 100644 (file)
@@ -7,7 +7,6 @@ import com.intellij.execution.JvmConfigurationOptions
 import com.intellij.execution.ShortenCommandLine
 import com.intellij.util.xmlb.annotations.OptionTag
 import com.intellij.util.xmlb.annotations.XMap
-import java.util.*
 
 open class JvmMainMethodRunConfigurationOptions : JvmConfigurationOptions() {
   @get:OptionTag("PROGRAM_PARAMETERS")
@@ -27,7 +26,7 @@ open class JvmMainMethodRunConfigurationOptions : JvmConfigurationOptions() {
 
   @Property(description = "Environment variables")
   @get:XMap(propertyElementName = "envs", entryTagName = "env", keyAttributeName = "name")
-  var env by map<String, String>(LinkedHashMap())
+  var env by linkedMap<String, String>()
 
   // see ConfigurationWithCommandLineShortener - "null if option was not selected explicitly, legacy user-local options to be used"
   // so, we cannot use NONE as default value
index e4f1234a0ca5d1c5164aa3aec80d5b38b36757af..e1b601753149fd7a73e1260f8a0116331bae6851 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.configurationStore
 
 import com.intellij.openapi.components.BaseState
@@ -6,25 +6,9 @@ import com.intellij.openapi.util.JDOMUtil
 import com.intellij.testFramework.assertions.Assertions.assertThat
 import com.intellij.util.xmlb.annotations.Attribute
 import com.intellij.util.xmlb.annotations.CollectionBean
+import com.intellij.util.xmlb.annotations.XMap
 import org.junit.Test
 
-internal class AState(languageLevel: String? = null, nestedComplex: NestedState? = null) : BaseState() {
-  @get:Attribute("customName")
-  var languageLevel by property(languageLevel)
-
-  var bar by string()
-
-  var property2 by property(0)
-
-  var floatProperty by property(0.3f)
-
-  var nestedComplex by property(nestedComplex)
-}
-
-internal class NestedState : BaseState() {
-  var childProperty by string()
-}
-
 class StoredPropertyStateTest {
   private class Foo : BaseState() {
     var bar by property<AState>()
@@ -102,12 +86,12 @@ class StoredPropertyStateTest {
 
   @Test
   fun listModificationCount() {
-    class UpdateOptions : BaseState() {
+    class TestOptions : BaseState() {
       @get:CollectionBean
       val pluginHosts by list<String>()
     }
 
-    val state = UpdateOptions()
+    val state = TestOptions()
     val oldModificationCount = state.modificationCount
 
     val list = state.pluginHosts
@@ -118,10 +102,68 @@ class StoredPropertyStateTest {
 
     val element = state.serialize()
     assertThat(element).isEqualTo("""
-    <UpdateOptions>
+    <TestOptions>
       <pluginHosts>
         <item value="foo" />
       </pluginHosts>
-    </UpdateOptions>""")
+    </TestOptions>""")
   }
+
+  @Test
+  fun `map modification count`() {
+    class TestOptions : BaseState() {
+      @get:XMap
+      val foo by map<String, String>()
+    }
+
+    val state = TestOptions()
+    var oldModificationCount = state.modificationCount
+
+    val list = state.foo
+    list.clear()
+    list.put("a", "b")
+    assertThat(state.modificationCount).isNotEqualTo(oldModificationCount)
+    assertThat(state.isEqualToDefault()).isFalse()
+
+    val element = state.serialize()
+    assertThat(element).isEqualTo("""
+    <TestOptions>
+      <foo>
+        <entry key="a" value="b" />
+      </foo>
+    </TestOptions>""")
+
+    oldModificationCount = state.modificationCount
+    list.clear()
+    assertThat(state.modificationCount).isNotEqualTo(oldModificationCount)
+    assertThat(state.isEqualToDefault()).isTrue()
+
+    oldModificationCount = state.modificationCount
+    list.put("a", "v")
+    list.put("b", "v")
+    assertThat(state.modificationCount).isNotEqualTo(oldModificationCount)
+    assertThat(state.isEqualToDefault()).isFalse()
+
+    oldModificationCount = state.modificationCount
+    list.remove("a")
+    assertThat(state.modificationCount).isNotEqualTo(oldModificationCount)
+    assertThat(state.isEqualToDefault()).isFalse()
+  }
+}
+
+internal class AState(languageLevel: String? = null, nestedComplex: NestedState? = null) : BaseState() {
+  @get:Attribute("customName")
+  var languageLevel by property(languageLevel)
+
+  var bar by string()
+
+  var property2 by property(0)
+
+  var floatProperty by property(0.3f)
+
+  var nestedComplex by property(nestedComplex)
+}
+
+internal class NestedState : BaseState() {
+  var childProperty by string()
 }
\ No newline at end of file
index 17e5272419d487ceb7cd4a086f80f6071f74de86..7033bf13e58c52d794de0bcde752c9137d00d855 100644 (file)
@@ -5,9 +5,12 @@ import com.intellij.openapi.components.BaseState
 import com.intellij.openapi.components.JsonSchemaType
 import com.intellij.openapi.components.StoredProperty
 import com.intellij.openapi.components.StoredPropertyBase
+import gnu.trove.THashMap
 import kotlin.reflect.KProperty
 
-class MapStoredProperty<K: Any, V>(private val value: MutableMap<K, V>) : StoredPropertyBase<MutableMap<K, V>>() {
+class MapStoredProperty<K: Any, V>(value: MutableMap<K, V>?) : StoredPropertyBase<MutableMap<K, V>>() {
+  private val value: MutableMap<K, V> = value ?: MyMap()
+
   override val jsonType: JsonSchemaType
     get() = JsonSchemaType.OBJECT
 
@@ -44,4 +47,35 @@ class MapStoredProperty<K: Any, V>(private val value: MutableMap<K, V>) : Stored
 
   @Suppress("FunctionName")
   fun __getValue() = value
+
+  override fun getModificationCount(): Long {
+    return when (value) {
+      is MyMap -> value.modificationCount
+      else -> super.getModificationCount()
+    }
+  }
+}
+
+private class MyMap<K: Any, V> : THashMap<K, V>() {
+  @Volatile
+  var modificationCount = 0L
+
+  override fun put(key: K, value: V): V? {
+    val oldValue = super.put(key, value)
+    if (oldValue !== value) {
+      modificationCount++
+    }
+    return oldValue
+  }
+
+  // to detect remove from iterator
+  override fun removeAt(index: Int) {
+    super.removeAt(index)
+    modificationCount++
+  }
+
+  override fun clear() {
+    super.clear()
+    modificationCount++
+  }
 }
\ No newline at end of file
index 3129fdf7d76b4b6ca3b121f73c9ed0c51cdcdba1..88e4045ab4d46042fe7c129db049a262544c483c 100644 (file)
@@ -8,13 +8,13 @@ import com.intellij.util.xmlb.Accessor
 import com.intellij.util.xmlb.PropertyAccessor
 import com.intellij.util.xmlb.SerializationFilter
 import com.intellij.util.xmlb.annotations.Transient
-import gnu.trove.THashMap
 import gnu.trove.THashSet
 import org.jetbrains.annotations.ApiStatus
 import java.nio.charset.Charset
 import java.util.*
 import java.util.concurrent.atomic.AtomicLongFieldUpdater
 import kotlin.collections.ArrayList
+import kotlin.collections.LinkedHashMap
 
 private val LOG = logger<BaseState>()
 
@@ -127,7 +127,20 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
     return result as StoredPropertyBase<MutableList<T>>
   }
 
-  protected fun <K : Any, V: Any> map(value: MutableMap<K, V> = THashMap()): StoredPropertyBase<MutableMap<K, V>> {
+  protected fun <K : Any, V: Any> map(): StoredPropertyBase<MutableMap<K, V>> {
+    val result = MapStoredProperty<K, V>(null)
+    addProperty(result)
+    return result
+  }
+
+  protected fun <K : Any, V: Any> linkedMap(): StoredPropertyBase<MutableMap<K, V>> {
+    val result = MapStoredProperty<K, V>(LinkedHashMap())
+    addProperty(result)
+    return result
+  }
+
+  @Deprecated(level = DeprecationLevel.ERROR, message = "Use map", replaceWith = ReplaceWith("map()"))
+  protected fun <K : Any, V: Any> map(value: MutableMap<K, V>): StoredPropertyBase<MutableMap<K, V>> {
     val result = MapStoredProperty(value)
     addProperty(result)
     return result
@@ -201,10 +214,10 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
   fun isEqualToDefault(): Boolean = properties.all { it.isEqualToDefault() }
 
   /**
-   * If you use [set], [treeSet] or [map], you must ensure that [incrementModificationCount] is called for each mutation operation on corresponding property value (e.g. add, remove).
+   * If you use [set], [treeSet] or [linkedMap], you must ensure that [incrementModificationCount] is called for each mutation operation on corresponding property value (e.g. add, remove).
    * Setting property to a new value updates modification count, but direct modification of mutable collection or map doesn't.
    *
-   * The only exclusion - [list].
+   * [list] and [map] track content mutation, but if key or value is not primitive value, you also have to [incrementModificationCount] in case of nested mutation.
    */
   @Transient
   override fun getModificationCount(): Long {
index 4c8231cb39a44441c26a8fe1df0b25bba09647a4..7b1f7de74b45f7ca9b85f555880f674cfc56df70 100644 (file)
@@ -6,13 +6,12 @@ import com.intellij.execution.configuration.EnvironmentVariablesData
 import com.intellij.openapi.components.BaseState
 import com.intellij.util.xmlb.annotations.Tag
 import com.intellij.util.xmlb.annotations.XMap
-import java.util.*
 
 @Tag("")
 class EnvironmentVariablesDataOptions : BaseState() {
   @Property(description = "Environment variables")
   @get:XMap(entryTagName = "env", keyAttributeName = "key")
-  var envs by map<String, String>(LinkedHashMap())
+  var envs by linkedMap<String, String>()
 
   var isPassParentEnvs by property(true)