configuration file — shared RC (not complete, part 2)
authorVladimir Krivosheev <vladimir.krivosheev@jetbrains.com>
Mon, 27 Aug 2018 15:47:17 +0000 (17:47 +0200)
committerVladimir Krivosheev <vladimir.krivosheev@jetbrains.com>
Mon, 27 Aug 2018 15:53:03 +0000 (17:53 +0200)
15 files changed:
platform/platform-tests/intellij.platform.tests.iml
platform/projectModel-api/src/com/intellij/configurationStore/properties/CollectionStoredProperty.kt
platform/projectModel-api/src/com/intellij/configurationStore/properties/FloatStoredProperty.kt
platform/projectModel-api/src/com/intellij/configurationStore/properties/IntStoredProperty.kt
platform/projectModel-api/src/com/intellij/configurationStore/properties/LongStoredProperty.kt
platform/projectModel-api/src/com/intellij/configurationStore/properties/ObjectStoredProperty.kt
platform/projectModel-api/src/com/intellij/configurationStore/properties/StringStoredProperty.kt
platform/projectModel-api/src/com/intellij/openapi/components/BaseState.kt
platform/projectModel-api/src/com/intellij/openapi/components/StoredPropertyBase.kt
plugins/configuration-script/intellij.configurationScript.iml
plugins/configuration-script/src/com/intellij/configurationScript/IntellijConfigurationAppInitializer.kt
plugins/configuration-script/src/com/intellij/configurationScript/IntellijConfigurationJsonSchemaProviderFactory.kt
plugins/configuration-script/src/com/intellij/configurationScript/RunConfigurationListReader.kt [new file with mode: 0644]
plugins/configuration-script/src/com/intellij/configurationScript/runConfigurations.kt
plugins/configuration-script/test/ConfigurationFileTest.kt

index 780a776340f562cd0a0edd68e98bf16352cebee6..4a8a32f782dc56fd2230554d1cc1b2e43f0d8517 100644 (file)
@@ -39,5 +39,6 @@
     <orderEntry type="library" scope="TEST" name="miglayout-swing" level="project" />
     <orderEntry type="library" scope="TEST" name="batik" level="project" />
     <orderEntry type="library" scope="TEST" name="xmlgraphics-commons" level="project" />
+    <orderEntry type="module" module-name="intellij.configurationScript" scope="TEST" />
   </component>
 </module>
\ No newline at end of file
index 8fc5087803b09a775c688ab1075ba2e7b6c44c9b..f58272c2e361ff7acfe11810b9b301c2e5532fa6 100644 (file)
@@ -2,6 +2,7 @@
 package com.intellij.configurationStore.properties
 
 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 com.intellij.util.SmartList
@@ -11,8 +12,8 @@ import kotlin.reflect.KProperty
  * AbstractCollectionBinding modifies collection directly, so, we cannot use null as default null and return empty list on get.
  */
 internal open class CollectionStoredProperty<E, C : MutableCollection<E>>(protected val value: C) : StoredPropertyBase<C>() {
-  override val jsonType: String
-    get() = "array"
+  override val jsonType: JsonSchemaType
+    get() = JsonSchemaType.ARRAY
 
   override fun isEqualToDefault() = value.isEmpty()
 
@@ -40,7 +41,7 @@ internal open class CollectionStoredProperty<E, C : MutableCollection<E>>(protec
 
   override fun toString() = "$name = ${if (isEqualToDefault()) "" else value.joinToString(" ")}"
 
-  override fun setValue(other: StoredProperty): Boolean {
+  override fun setValue(other: StoredProperty<C>): Boolean {
     @Suppress("UNCHECKED_CAST")
     return doSetValue(value, (other as CollectionStoredProperty<E, C>).value)
   }
@@ -51,8 +52,8 @@ internal class ListStoredProperty<T> : CollectionStoredProperty<T, SmartList<T>>
 }
 
 internal class MapStoredProperty<K: Any, V>(private val value: MutableMap<K, V>) : StoredPropertyBase<MutableMap<K, V>>() {
-  override val jsonType: String
-    get() = "object"
+  override val jsonType: JsonSchemaType
+    get() = JsonSchemaType.OBJECT
 
   override fun isEqualToDefault() = value.isEmpty()
 
@@ -80,7 +81,7 @@ internal class MapStoredProperty<K: Any, V>(private val value: MutableMap<K, V>)
 
   override fun toString() = if (isEqualToDefault()) "" else value.toString()
 
-  override fun setValue(other: StoredProperty): Boolean {
+  override fun setValue(other: StoredProperty<MutableMap<K, V>>): Boolean {
     @Suppress("UNCHECKED_CAST")
     return doSetValue(value, (other as MapStoredProperty<K, V>).value)
   }
index d97924501664e4fa29d6f13b03dc6cd88e1463c6..117fd902ba56a982b0a249f1c0a8284d13ed6b4b 100644 (file)
@@ -1,16 +1,15 @@
 // 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.
 package com.intellij.configurationStore.properties
 
-import com.intellij.openapi.components.BaseState
-import com.intellij.openapi.components.StoredProperty
-import com.intellij.openapi.components.StoredPropertyBase
+import com.intellij.openapi.components.*
+import com.intellij.openapi.util.text.StringUtil
 import kotlin.reflect.KProperty
 
-internal class FloatStoredProperty(private val defaultValue: Float, private val valueNormalizer: ((value: Float) -> Float)?) : StoredPropertyBase<Float>() {
+internal class FloatStoredProperty(private val defaultValue: Float, private val valueNormalizer: ((value: Float) -> Float)?) : StoredPropertyBase<Float>(), ScalarProperty {
   private var value = defaultValue
 
-  override val jsonType: String
-    get() = "number"
+  override val jsonType: JsonSchemaType
+    get() = JsonSchemaType.NUMBER
 
   override operator fun getValue(thisRef: BaseState, property: KProperty<*>) = value
 
@@ -22,7 +21,7 @@ internal class FloatStoredProperty(private val defaultValue: Float, private val
     }
   }
 
-  override fun setValue(other: StoredProperty): Boolean {
+  override fun setValue(other: StoredProperty<Float>): Boolean {
     val newValue = (other as FloatStoredProperty).value
     if (newValue == value) {
       return false
@@ -39,4 +38,44 @@ internal class FloatStoredProperty(private val defaultValue: Float, private val
   override fun toString() = "$name = $value${if (value == defaultValue) " (default)" else ""}"
 
   override fun isEqualToDefault() = value == defaultValue
+
+  override fun parseAndSetValue(rawValue: String?) {
+    if (rawValue == null) {
+      return
+    }
+
+    value = parseYamlFloat(rawValue)
+  }
+}
+
+private fun parseYamlFloat(_value: String): Float {
+  var value = StringUtil.replace(_value, "_", "")
+  var sign = +1
+  val first = value.get(0)
+  if (first == '-') {
+    sign = -1
+    value = value.substring(1)
+  }
+  else if (first == '+') {
+    value = value.substring(1)
+  }
+
+  return when {
+    StringUtil.equalsIgnoreCase(".inf", value) -> if (sign == -1) Float.NEGATIVE_INFINITY else Float.POSITIVE_INFINITY
+    StringUtil.equalsIgnoreCase(".nan", value) -> Float.NaN
+    value.indexOf(':') != -1 -> {
+      val digits = value.split(":")
+      var bes = 1
+      var v = 0.0F
+      var i = 0
+      val j = digits.size
+      while (i < j) {
+        v += (digits[j - i - 1]).toFloat() * bes
+        bes *= 60
+        i++
+      }
+      sign * v
+    }
+    else -> value.toFloat() * sign
+  }
 }
\ No newline at end of file
index e09ee872ea15c8f5cdd3be1b234e498aeb10ed98..8c8c9e85503a372274508bbbb2e873d20daaf2a0 100644 (file)
@@ -1,16 +1,15 @@
 // 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.
 package com.intellij.configurationStore.properties
 
-import com.intellij.openapi.components.BaseState
-import com.intellij.openapi.components.StoredProperty
-import com.intellij.openapi.components.StoredPropertyBase
+import com.intellij.openapi.components.*
+import com.intellij.openapi.util.text.StringUtil
 import kotlin.reflect.KProperty
 
-internal class IntStoredProperty(private val defaultValue: Int, private val valueNormalizer: ((value: Int) -> Int)?) : StoredPropertyBase<Int>() {
+internal class IntStoredProperty(private val defaultValue: Int, private val valueNormalizer: ((value: Int) -> Int)?) : StoredPropertyBase<Int>(), ScalarProperty {
   private var value = defaultValue
 
-  override val jsonType: String
-    get() = "integer"
+  override val jsonType: JsonSchemaType
+    get() = JsonSchemaType.INTEGER
 
   override operator fun getValue(thisRef: BaseState, property: KProperty<*>) = value
 
@@ -22,7 +21,7 @@ internal class IntStoredProperty(private val defaultValue: Int, private val valu
     }
   }
 
-  override fun setValue(other: StoredProperty): Boolean {
+  override fun setValue(other: StoredProperty<Int>): Boolean {
     val newValue = (other as IntStoredProperty).value
     if (newValue == value) {
       return false
@@ -39,4 +38,66 @@ internal class IntStoredProperty(private val defaultValue: Int, private val valu
   override fun toString() = "$name = $value${if (value == defaultValue) " (default)" else ""}"
 
   override fun isEqualToDefault() = value == defaultValue
+
+  override fun parseAndSetValue(rawValue: String?) {
+    if (rawValue == null) {
+      return
+    }
+
+    value = parseYamlInt(rawValue)
+  }
+}
+
+private fun parseYamlInt(_value: String): Int {
+  var value = StringUtil.replace(_value, "_", "")
+  var sign = +1
+  val first = value.get(0)
+  if (first == '-') {
+    sign = -1
+    value = value.substring(1)
+  }
+  else if (first == '+') {
+    value = value.substring(1)
+  }
+
+  if ("0" == value) {
+    return 0
+  }
+
+  return when {
+    value.startsWith("0b") -> {
+      createNumber(sign, value.substring(2), 2)
+    }
+
+    value.startsWith("0x") -> {
+      createNumber(sign, value.substring(2), 16)
+    }
+
+    value.startsWith("0") -> {
+      createNumber(sign, value.substring(1), 6)
+    }
+
+    value.indexOf(':') != -1 -> {
+      val digits = value.split(":")
+      var bes = 1
+      var v = 0
+      var i = 0
+      val j = digits.size
+      while (i < j) {
+        v += ((digits[j - i - 1]).toInt() * bes)
+        bes *= 60
+        i++
+      }
+      createNumber(sign, v.toString(), 10)
+    }
+    else -> createNumber(sign, value, 10)
+  }
+}
+
+private fun createNumber(sign: Int, _number: String, radix: Int): Int {
+  var number = _number
+  if (sign < 0) {
+    number = "-$number"
+  }
+  return number.toInt(radix)
 }
\ No newline at end of file
index 6a44ef643220196f91a38b03c9fbfccffd8fc757..2ef03c5614fd589a15e61607a15e0a5021bb27fb 100644 (file)
@@ -1,16 +1,15 @@
 // 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.
 package com.intellij.configurationStore.properties
 
-import com.intellij.openapi.components.BaseState
-import com.intellij.openapi.components.StoredProperty
-import com.intellij.openapi.components.StoredPropertyBase
+import com.intellij.openapi.components.*
+import com.intellij.openapi.util.text.StringUtil
 import kotlin.reflect.KProperty
 
-internal class LongStoredProperty(private val defaultValue: Long, private val valueNormalizer: ((value: Long) -> Long)?) : StoredPropertyBase<Long>() {
+internal class LongStoredProperty(private val defaultValue: Long, private val valueNormalizer: ((value: Long) -> Long)?) : StoredPropertyBase<Long>(), ScalarProperty {
   private var value = defaultValue
 
-  override val jsonType: String
-    get() = "integer"
+  override val jsonType: JsonSchemaType
+    get() = JsonSchemaType.INTEGER
 
   override operator fun getValue(thisRef: BaseState, property: KProperty<*>) = value
 
@@ -22,7 +21,7 @@ internal class LongStoredProperty(private val defaultValue: Long, private val va
     }
   }
 
-  override fun setValue(other: StoredProperty): Boolean {
+  override fun setValue(other: StoredProperty<Long>): Boolean {
     val newValue = (other as LongStoredProperty).value
     if (newValue == value) {
       return false
@@ -39,4 +38,66 @@ internal class LongStoredProperty(private val defaultValue: Long, private val va
   override fun toString() = "$name = $value${if (value == defaultValue) " (default)" else ""}"
 
   override fun isEqualToDefault() = value == defaultValue
+
+  override fun parseAndSetValue(rawValue: String?) {
+    if (rawValue == null) {
+      return
+    }
+
+    value = parseYamlLong(rawValue)
+  }
+}
+
+private fun parseYamlLong(_value: String): Long {
+  var value = StringUtil.replace(_value, "_", "")
+  var sign = +1
+  val first = value.get(0)
+  if (first == '-') {
+    sign = -1
+    value = value.substring(1)
+  }
+  else if (first == '+') {
+    value = value.substring(1)
+  }
+
+  if ("0" == value) {
+    return 0
+  }
+
+  return when {
+    value.startsWith("0b") -> {
+      createNumber(sign, value.substring(2), 2)
+    }
+
+    value.startsWith("0x") -> {
+      createNumber(sign, value.substring(2), 16)
+    }
+
+    value.startsWith("0") -> {
+      createNumber(sign, value.substring(1), 6)
+    }
+
+    value.indexOf(':') != -1 -> {
+      val digits = value.split(":")
+      var bes = 1
+      var v = 0L
+      var i = 0
+      val j = digits.size
+      while (i < j) {
+        v += ((digits[j - i - 1]).toLong() * bes)
+        bes *= 60
+        i++
+      }
+      createNumber(sign, v.toString(), 10)
+    }
+    else -> createNumber(sign, value, 10)
+  }
+}
+
+private fun createNumber(sign: Int, _number: String, radix: Int): Long {
+  var number = _number
+  if (sign < 0) {
+    number = "-$number"
+  }
+  return number.toLong(radix)
 }
\ No newline at end of file
index abf925c68c0ae61dfd737513951d5f6344077a67..b4df3632b7327c0688dee0809dd221f6973b430e 100644 (file)
@@ -1,15 +1,14 @@
 // 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.
 package com.intellij.configurationStore.properties
 
-import com.intellij.openapi.components.BaseState
-import com.intellij.openapi.components.StoredProperty
-import com.intellij.openapi.components.StoredPropertyBase
+import com.intellij.openapi.components.*
 import com.intellij.openapi.util.ModificationTracker
+import com.intellij.openapi.util.text.StringUtil
 import kotlin.reflect.KProperty
 
 internal abstract class ObjectStateStoredPropertyBase<T>(protected var value: T) : StoredPropertyBase<T>() {
-  override val jsonType: String
-    get() = "object"
+  override val jsonType: JsonSchemaType
+    get() = JsonSchemaType.OBJECT
 
   override operator fun getValue(thisRef: BaseState, property: KProperty<*>): T = value
 
@@ -20,7 +19,7 @@ internal abstract class ObjectStateStoredPropertyBase<T>(protected var value: T)
     }
   }
 
-  override fun setValue(other: StoredProperty): Boolean {
+  override fun setValue(other: StoredProperty<T>): Boolean {
     @Suppress("UNCHECKED_CAST")
     val newValue = (other as ObjectStateStoredPropertyBase<T>).value
     return if (newValue == value) {
@@ -39,9 +38,9 @@ internal abstract class ObjectStateStoredPropertyBase<T>(protected var value: T)
   override fun toString() = "$name = ${if (isEqualToDefault()) "" else value?.toString() ?: super.toString()}"
 }
 
-internal open class ObjectStoredProperty<T>(private val defaultValue: T) : ObjectStateStoredPropertyBase<T>(defaultValue) {
-  override val jsonType: String
-    get() = if (defaultValue is Boolean) "boolean" else "object"
+internal open class ObjectStoredProperty<T>(private val defaultValue: T) : ObjectStateStoredPropertyBase<T>(defaultValue), ScalarProperty {
+  override val jsonType: JsonSchemaType
+    get() = if (defaultValue is Boolean) JsonSchemaType.BOOLEAN else JsonSchemaType.OBJECT
 
   override fun isEqualToDefault(): Boolean {
     val value = value
@@ -49,6 +48,11 @@ internal open class ObjectStoredProperty<T>(private val defaultValue: T) : Objec
   }
 
   override fun getModificationCount() = (value as? ModificationTracker)?.modificationCount ?: 0
+
+  @Suppress("UNCHECKED_CAST")
+  override fun parseAndSetValue(rawValue: String?) {
+    value = (StringUtil.equalsIgnoreCase(rawValue, "true") || StringUtil.equalsIgnoreCase(rawValue, "yes") || StringUtil.equalsIgnoreCase(rawValue, "on")) as T
+  }
 }
 
 internal class StateObjectStoredProperty<T : BaseState?>(initialValue: T) : ObjectStateStoredPropertyBase<T>(initialValue) {
index 405eeb2fe86fa3a7a327febf6f4260cc3c432c69..5934091eb585e4f9332e6eb7671f23aeb00c43f1 100644 (file)
@@ -1,16 +1,14 @@
 // 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.
 package com.intellij.configurationStore.properties
 
-import com.intellij.openapi.components.BaseState
-import com.intellij.openapi.components.StoredProperty
-import com.intellij.openapi.components.StoredPropertyBase
+import com.intellij.openapi.components.*
 import kotlin.reflect.KProperty
 
-internal class NormalizedStringStoredProperty(private val defaultValue: String?) : StoredPropertyBase<String?>() {
+internal class NormalizedStringStoredProperty(private val defaultValue: String?) : StoredPropertyBase<String?>(), ScalarProperty {
   private var value = defaultValue
 
-  override val jsonType: String
-    get() = "string"
+  override val jsonType: JsonSchemaType
+    get() = JsonSchemaType.STRING
 
   override operator fun getValue(thisRef: BaseState, property: KProperty<*>) = value
 
@@ -22,7 +20,7 @@ internal class NormalizedStringStoredProperty(private val defaultValue: String?)
     }
   }
 
-  override fun setValue(other: StoredProperty): Boolean {
+  override fun setValue(other: StoredProperty<String?>): Boolean {
     val newValue = (other as NormalizedStringStoredProperty).value
     if (newValue == value) {
       return false
@@ -39,4 +37,8 @@ internal class NormalizedStringStoredProperty(private val defaultValue: String?)
   override fun isEqualToDefault() = value == defaultValue
 
   override fun toString() = "$name = $value${if (value == defaultValue) " (default)" else ""}"
+
+  override fun parseAndSetValue(rawValue: String?) {
+    value = rawValue
+  }
 }
\ No newline at end of file
index d06faa846e72d469d17ef9f12f01b5e4aca5ae7c..d5151f17e3e37833a94b1eaaedabed59185baab8 100644 (file)
@@ -21,15 +21,20 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
     private val MOD_COUNT_UPDATER = AtomicLongFieldUpdater.newUpdater(BaseState::class.java, "ownModificationCount")
   }
 
-  private val properties: MutableList<StoredProperty> = SmartList()
+  private val properties: MutableList<StoredProperty<Any>> = SmartList()
 
   @Volatile
   @Transient
   private var ownModificationCount: Long = 0
 
+  private fun addProperty(p: StoredProperty<*>) {
+    @Suppress("UNCHECKED_CAST")
+    properties.add(p as StoredProperty<Any>)
+  }
+
   fun <T> property(): StoredPropertyBase<T?> {
     val result = ObjectStoredProperty<T?>(null)
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
@@ -39,7 +44,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
    */
   fun <T : BaseState?> property(initialValue: T): StoredPropertyBase<T> {
     val result = StateObjectStoredProperty(initialValue)
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
@@ -51,7 +56,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
       override fun isEqualToDefault() = isDefault(value)
     }
 
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
@@ -61,7 +66,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
    */
   fun <E, C : MutableCollection<E>> property(initialValue: C): StoredPropertyBase<C> {
     val result = CollectionStoredProperty(initialValue)
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
@@ -70,7 +75,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
    */
   fun <T : Charset> property(initialValue: T): StoredPropertyBase<T> {
     val result = ObjectStoredProperty(initialValue)
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
@@ -79,7 +84,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
    */
   fun <T : Enum<*>> property(defaultValue: T): StoredPropertyBase<T> {
     val result = ObjectStoredProperty(defaultValue)
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
@@ -88,7 +93,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
    */
   fun <T : Any> list(): StoredPropertyBase<MutableList<T>> {
     val result = ListStoredProperty<T>()
-    properties.add(result)
+    addProperty(result)
     @Suppress("UNCHECKED_CAST")
     return result as StoredPropertyBase<MutableList<T>>
   }
@@ -99,7 +104,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
 
   fun <K : Any, V: Any> map(value: MutableMap<K, V> = THashMap()): StoredPropertyBase<MutableMap<K, V>> {
     val result = MapStoredProperty(value)
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
@@ -113,31 +118,31 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
    */
   fun string(defaultValue: String? = null): StoredPropertyBase<String?> {
     val result = NormalizedStringStoredProperty(defaultValue)
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
   fun property(defaultValue: Int = 0): StoredPropertyBase<Int> {
     val result = IntStoredProperty(defaultValue, null)
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
   fun property(defaultValue: Long = 0): StoredPropertyBase<Long> {
     val result = LongStoredProperty(defaultValue, null)
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
   fun property(defaultValue: Float = 0f, valueNormalizer: ((value: Float) -> Float)? = null): StoredPropertyBase<Float> {
     val result = FloatStoredProperty(defaultValue, valueNormalizer)
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
   fun property(defaultValue: Boolean = false): StoredPropertyBase<Boolean> {
     val result = ObjectStoredProperty(defaultValue)
-    properties.add(result)
+    addProperty(result)
     return result
   }
 
@@ -170,20 +175,6 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
 
   fun isEqualToDefault(): Boolean = properties.all { it.isEqualToDefault() }
 
-  // internal usage only
-  @ApiStatus.Experimental
-  fun buildJsonSchema(builder: StringBuilder) {
-    // todo object definition
-    for (property in properties) {
-      builder.jsonEscapedString(property.name!!).append(':').append('{')
-      builder.jsonEscapedString("type").append(':').jsonEscapedString(property.jsonType)
-      builder.append('}')
-      if (property !== properties.last()) {
-        builder.append(',')
-      }
-    }
-  }
-
   @Transient
   override fun getModificationCount(): Long {
     var result = ownModificationCount
@@ -225,6 +216,24 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
       incrementModificationCount()
     }
   }
+
+  fun getProperties() = properties
+}
+
+// move buildJsonSchema and other such functions from BaseState to exclude from completion
+// internal usage only
+@ApiStatus.Experimental
+fun buildJsonSchema(state: BaseState, builder: StringBuilder) {
+  val properties = state.getProperties()
+  // todo object definition
+  for (property in properties) {
+    builder.jsonEscapedString(property.name!!).append(':').append('{')
+    builder.jsonEscapedString("type").append(':').jsonEscapedString(property.jsonType.jsonName)
+    builder.append('}')
+    if (property !== properties.last()) {
+      builder.append(',')
+    }
+  }
 }
 
 private fun StringBuilder.jsonEscapedString(value: String): StringBuilder {
index 0b59407ed70f9de557f9562cac4751dc62bfeca1..7a9b55d68dd961444123182116598c7825cebb27 100644 (file)
@@ -1,28 +1,50 @@
 // 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.
 package com.intellij.openapi.components
 
+import org.jetbrains.annotations.ApiStatus
 import kotlin.properties.ReadWriteProperty
 import kotlin.reflect.KProperty
 
-internal interface StoredProperty {
+@ApiStatus.Experimental
+interface StoredProperty<T> {
   var name: String?
 
-  val jsonType: String
+  val jsonType: JsonSchemaType
 
   // true if changed
-  fun setValue(other: StoredProperty): Boolean
+  fun setValue(other: StoredProperty<T>): Boolean
 
   fun isEqualToDefault(): Boolean
 
   fun getModificationCount(): Long = 0
 }
 
+interface ScalarProperty {
+  // mod count not changed
+  fun parseAndSetValue(rawValue: String?)
+}
+
 // type must be exposed otherwise `provideDelegate` doesn't work
-abstract class StoredPropertyBase<T> : ReadWriteProperty<BaseState, T>, StoredProperty {
+abstract class StoredPropertyBase<T> : StoredProperty<T>, ReadWriteProperty<BaseState, T> {
   override var name: String? = null
 
   operator fun provideDelegate(thisRef: Any, property: KProperty<*>): ReadWriteProperty<BaseState, T> {
     name = property.name
     return this
   }
+}
+
+enum class JsonSchemaType(val jsonName: String) {
+  OBJECT("object"),
+  ARRAY("array"),
+
+  STRING("string"),
+
+  INTEGER("integer"),
+  NUMBER("number"),
+
+  BOOLEAN("boolean");
+
+  val isScalar: Boolean
+    get() = this != OBJECT && this !== ARRAY
 }
\ No newline at end of file
index daf3696a70491c97e4537fe5b75dfdac0bb02234..cd851be9625a9cb9e50042ecd19f183199d446dc 100644 (file)
@@ -18,5 +18,6 @@
     <orderEntry type="module" module-name="intellij.platform.ide.impl" />
     <orderEntry type="module" module-name="intellij.platform.lang.impl" />
     <orderEntry type="library" name="snakeyaml" level="project" />
+    <orderEntry type="module" module-name="intellij.java.execution.impl" scope="TEST" />
   </component>
 </module>
\ No newline at end of file
index be7a08615403269d4e932477461ebb60079690d2..30d0735718cc85dec9a48e26c5a202e0bba6cadf 100644 (file)
@@ -2,21 +2,17 @@ package com.intellij.configurationScript
 
 import com.intellij.execution.RunManager
 import com.intellij.execution.RunManagerListener
-import com.intellij.execution.configurations.ConfigurationType
+import com.intellij.execution.configurations.ConfigurationFactory
 import com.intellij.execution.impl.RunManagerImpl
 import com.intellij.ide.ApplicationInitializedListener
 import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.components.BaseState
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.util.registry.Registry
-import com.intellij.project.stateStore
 import com.intellij.util.io.exists
 import com.intellij.util.io.inputStreamIfExists
-import gnu.trove.THashMap
 import org.yaml.snakeyaml.Yaml
 import org.yaml.snakeyaml.constructor.SafeConstructor
 import org.yaml.snakeyaml.nodes.MappingNode
-import org.yaml.snakeyaml.nodes.Node
 import org.yaml.snakeyaml.nodes.ScalarNode
 import java.io.Reader
 import java.nio.file.Path
@@ -35,7 +31,7 @@ internal class IntellijConfigurationAppInitializer : ApplicationInitializedListe
         val file = findConfigurationFile(project) ?: return
         val inputStream = file.inputStreamIfExists() ?: return
         inputStream.use {
-          parseConfigurationFile(it.bufferedReader())
+          parseConfigurationFile(it.bufferedReader()) { _, _ -> }
         }
       }
     })
@@ -44,64 +40,33 @@ internal class IntellijConfigurationAppInitializer : ApplicationInitializedListe
 
 // we cannot use the same approach as we generate JSON scheme because we should load option classes only in a lazy manner
 // that's why we don't use snakeyaml TypeDescription approach to load
-internal fun parseConfigurationFile(reader: Reader): Any? {
-  val constructor = MyConstructor()
-  val yaml = Yaml(constructor)
+internal fun parseConfigurationFile(reader: Reader, processor: (factory: ConfigurationFactory, state: Any) -> Unit) {
+  val yaml = Yaml(SafeConstructor())
   // later we can avoid full node graph building, but for now just use simple implementation (problem is that Yaml supports references and merge - proper support of it can be tricky)
   // "load" under the hood uses "compose" - i.e. Yaml itself doesn't use stream API to build object model.
-  val rootNode = yaml.compose(reader) as? MappingNode ?: return null
-  for (node in rootNode.value) {
-    val keyNode = node.keyNode
+  val rootNode = yaml.compose(reader) as? MappingNode ?: return
+  val dataReader = RunConfigurationListReader(processor)
+  for (tuple in rootNode.value) {
+    val keyNode = tuple.keyNode
     if (keyNode is ScalarNode && keyNode.value == Keys.runConfigurations) {
-      val rcTypeGroupNode = node.valueNode as? MappingNode ?: continue
-      readRunConfigurationsNode(rcTypeGroupNode)
-      val document = constructor.doConstructDocument(node.valueNode)
-      return document
+      val rcTypeGroupNode = tuple.valueNode as? MappingNode ?: continue
+      dataReader.read(rcTypeGroupNode)
     }
   }
-  return rootNode
-}
-
-private fun readRunConfigurationsNode(parentNode: MappingNode) {
-  val keyToType = THashMap<String, ConfigurationType>()
-  processConfigurationTypes { configurationType, propertyName, _ ->
-    keyToType.put(propertyName.toString(), configurationType)
-  }
-  for (node in parentNode.value) {
-    val keyNode = node.keyNode as? ScalarNode ?: continue
-    //
-  }
-}
-
-private class MyConstructor : SafeConstructor() {
-  override fun newInstance(node: Node): Any {
-    return super.newInstance(node)
-  }
-
-  fun doConstructDocument(node: Node): Any? {
-    return constructDocument(node)
-  }
-
-  override fun createDefaultMap(initSize: Int): MutableMap<Any, Any> {
-    // order not important because actual class instance will be created instead of untyped map
-    // THashMap is more efficient than LinkedHashMap
-    return THashMap(initSize)
-  }
-}
-
-private class ConfigurationFileRoot {
-  var runConfigurations: List<BaseState>? = null
 }
 
 /**
  * not-null doesn't mean that you should not expect NoSuchFileException
  */
 private fun findConfigurationFile(project: Project): Path? {
-  val projectIdeaDir = Paths.get(project.stateStore.directoryStorePath)
+  val projectIdeaDir = Paths.get(project.basePath)
   var file = projectIdeaDir.resolve("intellij.yaml")
   if (!file.exists()) {
     // do not check file exists - on read we in any case should check NoSuchFileException
     file = projectIdeaDir.resolve("intellij.yml")
   }
   return file
-}
\ No newline at end of file
+}
+
+internal const val IDE_FILE = "intellij.yaml"
+internal const val IDE_FILE_VARIANT_2 = "intellij.yml"
\ No newline at end of file
index 51596c4c8b0886dfe4b1fc968b7ef74d982ab1db..779e07098907ddefa6879028fecac1e055d52f65 100644 (file)
@@ -20,13 +20,11 @@ internal val LOG = logger<IntellijConfigurationJsonSchemaProviderFactory>()
 internal class IntellijConfigurationJsonSchemaProviderFactory : JsonSchemaProviderFactory, JsonSchemaFileProvider {
   private val schemeFile: VirtualFile by lazy { generateConfigurationSchema() }
 
-  override fun getProviders(project: Project): List<JsonSchemaFileProvider> {
-    return listOf(this)
-  }
+  override fun getProviders(project: Project) = listOf(this)
 
   override fun isAvailable(file: VirtualFile): Boolean {
     val nameSequence = file.nameSequence
-    return (nameSequence.endsWith(".yaml") || nameSequence.endsWith(".yml")) && StringUtil.equals(file.parent?.nameSequence, Project.DIRECTORY_STORE_FOLDER)
+    return StringUtil.equals(nameSequence, IDE_FILE) || StringUtil.equals(nameSequence, IDE_FILE_VARIANT_2)
   }
 
   override fun getName() = "IntelliJ Configuration"
diff --git a/plugins/configuration-script/src/com/intellij/configurationScript/RunConfigurationListReader.kt b/plugins/configuration-script/src/com/intellij/configurationScript/RunConfigurationListReader.kt
new file mode 100644 (file)
index 0000000..e92f21e
--- /dev/null
@@ -0,0 +1,117 @@
+package com.intellij.configurationScript
+
+import com.intellij.execution.configurations.ConfigurationFactory
+import com.intellij.execution.configurations.ConfigurationType
+import com.intellij.openapi.components.BaseState
+import com.intellij.openapi.components.ScalarProperty
+import com.intellij.openapi.diagnostic.debug
+import com.intellij.openapi.diagnostic.runAndLogException
+import com.intellij.util.ReflectionUtil
+import gnu.trove.THashMap
+import org.yaml.snakeyaml.nodes.MappingNode
+import org.yaml.snakeyaml.nodes.Node
+import org.yaml.snakeyaml.nodes.ScalarNode
+import org.yaml.snakeyaml.nodes.SequenceNode
+
+internal class RunConfigurationListReader(private val processor: (factory: ConfigurationFactory, state: Any) -> Unit) {
+  // rc grouped by type
+  fun read(parentNode: MappingNode) {
+    val keyToType = THashMap<String, ConfigurationType>()
+    processConfigurationTypes { configurationType, propertyName, _ ->
+      keyToType.put(propertyName.toString(), configurationType)
+    }
+
+    for (tuple in parentNode.value) {
+      val keyNode = tuple.keyNode
+      if (keyNode !is ScalarNode) {
+        LOG.warn("Unexpected keyNode type: ${keyNode.nodeId}")
+        continue
+      }
+
+      val configurationType = keyToType.get(keyNode.value)
+      if (configurationType == null) {
+        LOG.warn("Unknown run configuration type: ${keyNode.value}")
+        continue
+      }
+
+      val factories = configurationType.configurationFactories
+      if (factories.isEmpty()) {
+        continue
+      }
+
+      val valueNode = tuple.valueNode
+
+      if (factories.size > 1) {
+        if (valueNode !is MappingNode) {
+          LOG.warn("Unexpected valueNode type: ${valueNode.nodeId}")
+          continue
+        }
+
+        readFactoryGroup(valueNode, configurationType)
+      }
+      else {
+        readRunConfigurationGroup(tuple.valueNode, factories.first())
+      }
+    }
+  }
+
+  // rc grouped by factory (nested group) if more than one factory
+  private fun readFactoryGroup(parentNode: MappingNode, type: ConfigurationType) {
+    for (tuple in parentNode.value) {
+      val keyNode = tuple.keyNode
+      if (keyNode !is ScalarNode) {
+        LOG.warn("Unexpected keyNode type: ${keyNode.nodeId}")
+        continue
+      }
+
+      val factoryKey = keyNode.value
+      val factory = type.configurationFactories.find { factory -> factoryKey == rcFactoryIdToPropertyName(factory) }
+      if (factory == null) {
+        LOG.warn("Unknown run configuration factory: ${keyNode.value}")
+        continue
+      }
+
+      readRunConfigurationGroup(tuple.valueNode, factory)
+    }
+  }
+
+  private fun readRunConfigurationGroup(node: Node, factory: ConfigurationFactory) {
+    val optionsClass = factory.optionsClass
+    if (optionsClass == null) {
+      LOG.debug { "Configuration factory \"${factory.name}\" is not described because options class not defined" }
+      return
+    }
+
+    if (node is MappingNode) {
+      // direct child
+      LOG.runAndLogException {
+        readRc(optionsClass, node, factory)
+      }
+    }
+    else if (node is SequenceNode) {
+      // array of child
+      for (itemNode in node.value) {
+        if (itemNode is MappingNode) {
+          readRc(optionsClass, itemNode, factory)
+        }
+      }
+    }
+  }
+
+  private fun readRc(optionsClass: Class<out BaseState>, node: MappingNode, factory: ConfigurationFactory) {
+    val state = ReflectionUtil.newInstance(optionsClass)
+    val properties = state.getProperties()
+    for (tuple in node.value) {
+      val valueNode = tuple.valueNode
+      val key = (tuple.keyNode as ScalarNode).value
+      if (valueNode is ScalarNode) {
+        for (property in properties) {
+          if (property is ScalarProperty && property.jsonType.isScalar && key == property.name) {
+            property.parseAndSetValue(valueNode.value)
+          }
+        }
+      }
+    }
+    processor(factory, state)
+  }
+}
\ No newline at end of file
index 17182294f0478acee70646488fc34b07b4475579..63a10094501ab46a7489c4bf56d1470961cffac1 100644 (file)
@@ -2,6 +2,7 @@ package com.intellij.configurationScript
 
 import com.intellij.execution.configurations.ConfigurationFactory
 import com.intellij.execution.configurations.ConfigurationType
+import com.intellij.openapi.components.buildJsonSchema
 import com.intellij.openapi.diagnostic.debug
 import com.intellij.openapi.util.text.StringUtil
 import com.intellij.util.ReflectionUtil
@@ -64,7 +65,7 @@ private fun describeFactories(configurationType: ConfigurationType, definitions:
   if (factories.size > 1) {
     for (factory in factories) {
       rcProperties.append("""
-          "${idToPropertyName(factory.id, null, factory)}": {
+          "${rcFactoryIdToPropertyName(factory)}": {
             "type": "object"
           },
         """.trimIndent())
@@ -88,7 +89,7 @@ private fun describeFactories(configurationType: ConfigurationType, definitions:
 
   val state = ReflectionUtil.newInstance(optionsClass)
   val stateProperties = StringBuilder()
-  state.buildJsonSchema(stateProperties)
+  buildJsonSchema(state, stateProperties)
 
   definitions.append("""
     "$definitionId": {
@@ -106,6 +107,11 @@ internal fun rcTypeIdToPropertyName(configurationType: ConfigurationType): CharS
 }
 
 // returns null if id is not valid
+internal fun rcFactoryIdToPropertyName(factory: ConfigurationFactory): CharSequence? {
+  return idToPropertyName(factory.id, null, factory)
+}
+
+// returns null if id is not valid
 private fun idToPropertyName(string: String, configurationType: ConfigurationType?, factory: ConfigurationFactory?): CharSequence? {
   val result = string
     .removeSuffix("Type")
index 37b24e1edc3f836daf5287f3e20760062235b54a..a07f25c27648917c573fa15e3ab0c02c97cfc4bc 100644 (file)
@@ -1,14 +1,22 @@
 package com.intellij.configurationScript
 
+import com.intellij.execution.application.ApplicationConfigurationOptions
 import com.intellij.execution.configurations.ConfigurationTypeBase
+import com.intellij.testFramework.ProjectRule
 import com.intellij.testFramework.assertions.Assertions.assertThat
-import gnu.trove.THashMap
-import org.assertj.core.data.MapEntry
+import com.intellij.util.SmartList
 import org.intellij.lang.annotations.Language
+import org.junit.ClassRule
 import org.junit.Test
 import javax.swing.Icon
 
 class ConfigurationFileTest {
+  companion object {
+    @JvmField
+    @ClassRule
+    val projectRule = ProjectRule()
+  }
+
   @Test
   fun rcId() {
     fun convert(string: String): String {
@@ -32,36 +40,59 @@ class ConfigurationFileTest {
     val result = parse("""
     runConfigurations:
     """)
-//    assertThat((node as MappingNode).value.map { (it.keyNode as ScalarNode).value }).containsExactly("runConfigurations")
-    assertThat(result).isNull()
+    assertThat(result).isEmpty()
+  }
+
+  @Test
+  fun `empty rc type group`() {
+    val result = parse("""
+    runConfigurations:
+      jvmMainMethod:
+    """)
+    assertThat(result).isEmpty()
   }
 
   @Test
   fun `empty rc`() {
     val result = parse("""
     runConfigurations:
-      jvmApp:
+      jvmMainMethod:
+        -
     """)
-    assertThat(result).isInstanceOf(THashMap::class.java)
-    @Suppress("UNCHECKED_CAST")
-    assertThat(result as Map<String, Any>).containsExactly(MapEntry.entry("jvmApp", null))
+    assertThat(result).isEmpty()
   }
 
   @Test
-  fun `one jvmApp`() {
+  fun `one jvmMainMethod`() {
     val result = parse("""
     runConfigurations:
-      jvmApp:
+      jvmMainMethod:
         isAlternativeJrePathEnabled: true
     """)
-    assertThat(result).isInstanceOf(THashMap::class.java)
-    @Suppress("UNCHECKED_CAST")
-    assertThat(result as Map<String, Any>).containsExactly(MapEntry.entry("jvmApp", null))
+    val options = ApplicationConfigurationOptions()
+    options.isAlternativeJrePathEnabled = true
+    assertThat(result).containsExactly(options)
+  }
+
+  @Test
+  fun `one jvmMainMethod as list`() {
+    val result = parse("""
+    runConfigurations:
+      jvmMainMethod:
+        - isAlternativeJrePathEnabled: true
+    """)
+    val options = ApplicationConfigurationOptions()
+    options.isAlternativeJrePathEnabled = true
+    assertThat(result).containsExactly(options)
   }
 }
 
-private fun parse(@Language("YAML") data: String): Any? {
-  return parseConfigurationFile(data.trimIndent().reader())
+private fun parse(@Language("YAML") data: String): List<Any> {
+  val list = SmartList<Any>()
+  parseConfigurationFile(data.trimIndent().reader()) { _, state ->
+    list.add(state)
+  }
+  return list
 }
 
 private class TestConfigurationType(id: String) : ConfigurationTypeBase(id, id, "", null as Icon?)
\ No newline at end of file