diff --git a/java/execution/impl/src/com/intellij/execution/JavaTestConfigurationBase.java b/java/execution/impl/src/com/intellij/execution/JavaTestConfigurationBase.java index 8b1071d46365..c62e4df950dd 100644 --- a/java/execution/impl/src/com/intellij/execution/JavaTestConfigurationBase.java +++ b/java/execution/impl/src/com/intellij/execution/JavaTestConfigurationBase.java @@ -61,7 +61,7 @@ public abstract class JavaTestConfigurationBase extends ModuleBasedConfiguration } @Override - public void setShortenCommandLine(ShortenCommandLine shortenCommandLine) { + public void setShortenCommandLine(@Nullable ShortenCommandLine shortenCommandLine) { myShortenCommandLine = shortenCommandLine; } diff --git a/java/execution/impl/src/com/intellij/execution/application/ApplicationConfiguration.java b/java/execution/impl/src/com/intellij/execution/application/ApplicationConfiguration.java index b8b59f2c269b..7e68cc96c331 100644 --- a/java/execution/impl/src/com/intellij/execution/application/ApplicationConfiguration.java +++ b/java/execution/impl/src/com/intellij/execution/application/ApplicationConfiguration.java @@ -9,6 +9,7 @@ import com.intellij.execution.junit.RefactoringListeners; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.util.JavaParametersUtil; import com.intellij.execution.util.ProgramParametersUtil; +import com.intellij.openapi.components.BaseState; import com.intellij.openapi.module.Module; import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.options.SettingsEditorGroup; @@ -35,14 +36,18 @@ public class ApplicationConfiguration extends ModuleBasedConfiguration by property(LinkedHashMap()) + + // see ConfigurationWithCommandLineShortener - "null if option was not selected explicitly, legacy user-local options to be used" + // so, we cannot use NONE as default value + @get:OptionTag(nameAttribute = "", valueAttribute = "name") + var shortenClasspath by enum() } \ No newline at end of file diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java index 5882168506e6..c0f5d5b8a232 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java @@ -238,7 +238,7 @@ public abstract class TestDiscoveryConfigurationProducer extends JavaRunConfigur } @Override - public void setShortenCommandLine(ShortenCommandLine mode) { + public void setShortenCommandLine(@Nullable ShortenCommandLine mode) { myConfiguration.setShortenCommandLine(mode); } diff --git a/platform/lang-api/src/com/intellij/execution/ShortenCommandLine.java b/platform/lang-api/src/com/intellij/execution/ShortenCommandLine.java index 071b77d9b38b..dbc2bcb19587 100644 --- a/platform/lang-api/src/com/intellij/execution/ShortenCommandLine.java +++ b/platform/lang-api/src/com/intellij/execution/ShortenCommandLine.java @@ -1,16 +1,4 @@ -// 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. +// 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.execution; import com.intellij.openapi.project.Project; @@ -74,6 +62,7 @@ public enum ShortenCommandLine { return null; } + @Deprecated public static void writeShortenClasspathMethod(@NotNull Element element, ShortenCommandLine shortenCommandLine) { if (shortenCommandLine != null) { element.addContent(new Element("shortenClasspath").setAttribute("name", shortenCommandLine.name())); diff --git a/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java b/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java index 91d791b83257..55065a910c6c 100644 --- a/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java +++ b/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java @@ -21,6 +21,7 @@ import com.intellij.util.SmartList; import com.intellij.util.xmlb.annotations.Attribute; import com.intellij.util.xmlb.annotations.Transient; import org.jdom.Element; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -215,7 +216,8 @@ public abstract class RunConfigurationBase extends UserDataHolderBase implements myOptions = XmlSerializer.deserialize(element, getOptionsClass()); } - public final void setState(@NotNull BaseState state) { + @ApiStatus.Experimental + public void setState(@NotNull BaseState state) { myOptions = (RunConfigurationOptions)state; } diff --git a/platform/lang-impl/src/com/intellij/execution/ConfigurationWithCommandLineShortener.java b/platform/lang-impl/src/com/intellij/execution/ConfigurationWithCommandLineShortener.java index 9055106d22f5..65f655c5db41 100644 --- a/platform/lang-impl/src/com/intellij/execution/ConfigurationWithCommandLineShortener.java +++ b/platform/lang-impl/src/com/intellij/execution/ConfigurationWithCommandLineShortener.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 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-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.execution; import com.intellij.openapi.project.Project; @@ -14,7 +14,7 @@ public interface ConfigurationWithCommandLineShortener { /** * Called from UI, when user explicitly selects method to be used to shorten the command line or from the deserialization */ - void setShortenCommandLine(ShortenCommandLine mode); + void setShortenCommandLine(@Nullable ShortenCommandLine mode); Project getProject(); } diff --git a/platform/projectModel-api/src/com/intellij/configurationStore/properties/ObjectStoredProperty.kt b/platform/projectModel-api/src/com/intellij/configurationStore/properties/ObjectStoredProperty.kt index b4df3632b732..f7222d35dfd6 100644 --- a/platform/projectModel-api/src/com/intellij/configurationStore/properties/ObjectStoredProperty.kt +++ b/platform/projectModel-api/src/com/intellij/configurationStore/properties/ObjectStoredProperty.kt @@ -4,9 +4,10 @@ package com.intellij.configurationStore.properties import com.intellij.openapi.components.* import com.intellij.openapi.util.ModificationTracker import com.intellij.openapi.util.text.StringUtil +import com.intellij.util.xmlb.XmlSerializerUtil import kotlin.reflect.KProperty -internal abstract class ObjectStateStoredPropertyBase(protected var value: T) : StoredPropertyBase() { +abstract class ObjectStateStoredPropertyBase(protected var value: T) : StoredPropertyBase() { override val jsonType: JsonSchemaType get() = JsonSchemaType.OBJECT @@ -55,6 +56,33 @@ internal open class ObjectStoredProperty(private val defaultValue: T) : Objec } } +class EnumStoredProperty>(private val defaultValue: T?, val clazz: Class) : ObjectStateStoredPropertyBase(defaultValue), ScalarProperty { + override val jsonType: JsonSchemaType + get() = JsonSchemaType.STRING + + override fun isEqualToDefault() = value === defaultValue + + override fun getModificationCount() = 0L + + override fun setValue(thisRef: BaseState, property: KProperty<*>, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") newValue: T?) { + val v = newValue ?: defaultValue + if (value !== v) { + thisRef.intIncrementModificationCount() + value = v + } + } + + @Suppress("UNCHECKED_CAST") + override fun parseAndSetValue(rawValue: String?) { + if (rawValue == null) { + value = defaultValue + } + else { + value = XmlSerializerUtil.stringToEnum(rawValue, clazz, true /* lowercase in YAML by default */) as T? ?: defaultValue + } + } +} + internal class StateObjectStoredProperty(initialValue: T) : ObjectStateStoredPropertyBase(initialValue) { override fun isEqualToDefault(): Boolean { val value = value diff --git a/platform/projectModel-api/src/com/intellij/openapi/components/BaseState.kt b/platform/projectModel-api/src/com/intellij/openapi/components/BaseState.kt index d5151f17e3e3..a269963a5d54 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/components/BaseState.kt +++ b/platform/projectModel-api/src/com/intellij/openapi/components/BaseState.kt @@ -32,7 +32,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker { properties.add(p as StoredProperty) } - fun property(): StoredPropertyBase { + protected fun property(): StoredPropertyBase { val result = ObjectStoredProperty(null) addProperty(result) return result @@ -42,7 +42,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker { * Value considered as default only if all properties have default values. * Passed instance is not used for `isDefault` check. It is just an initial value. */ - fun property(initialValue: T): StoredPropertyBase { + protected fun property(initialValue: T): StoredPropertyBase { val result = StateObjectStoredProperty(initialValue) addProperty(result) return result @@ -51,7 +51,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker { /** * For non-BaseState classes explicit `isDefault` must be provided, because no other way to check. */ - fun property(initialValue: T, isDefault: (value: T) -> Boolean): StoredPropertyBase { + protected fun property(initialValue: T, isDefault: (value: T) -> Boolean): StoredPropertyBase { val result = object : ObjectStoredProperty(initialValue) { override fun isEqualToDefault() = isDefault(value) } @@ -64,7 +64,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker { * Collection considered as default if empty. It is *your* responsibility to call `incrementModificationCount` on collection modification. * You cannot set value to a new collection - on set current collection is cleared and new collection is added to current. */ - fun > property(initialValue: C): StoredPropertyBase { + protected fun > property(initialValue: C): StoredPropertyBase { val result = CollectionStoredProperty(initialValue) addProperty(result) return result @@ -73,7 +73,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker { /** * Charset is an immutable, so, it is safe to use it as default value. */ - fun property(initialValue: T): StoredPropertyBase { + protected fun property(initialValue: T): StoredPropertyBase { val result = ObjectStoredProperty(initialValue) addProperty(result) return result @@ -82,27 +82,41 @@ abstract class BaseState : SerializationFilter, ModificationTracker { /** * Enum is an immutable, so, it is safe to use it as default value. */ - fun > property(defaultValue: T): StoredPropertyBase { + protected fun > property(defaultValue: T): StoredPropertyBase { val result = ObjectStoredProperty(defaultValue) addProperty(result) return result } + /** + * `null` is always normalized to null. + */ + protected inline fun > enum(defaultValue: T? = null): StoredPropertyBase { + return doEnum(defaultValue, T::class.java) + } + + @PublishedApi + internal fun > doEnum(defaultValue: T? = null, clazz: Class): StoredPropertyBase { + val result = EnumStoredProperty(defaultValue, clazz) + addProperty(result) + return result + } + /** * Not-null list. Initialized as SmartList. */ - fun list(): StoredPropertyBase> { + protected fun list(): StoredPropertyBase> { val result = ListStoredProperty() addProperty(result) @Suppress("UNCHECKED_CAST") return result as StoredPropertyBase> } - fun property(value: MutableMap): StoredPropertyBase> { + protected fun property(value: MutableMap): StoredPropertyBase> { return map(value) } - fun map(value: MutableMap = THashMap()): StoredPropertyBase> { + protected fun map(value: MutableMap = THashMap()): StoredPropertyBase> { val result = MapStoredProperty(value) addProperty(result) return result @@ -111,36 +125,36 @@ abstract class BaseState : SerializationFilter, ModificationTracker { /** * Empty string is always normalized to null. */ - fun property(defaultValue: String?): StoredPropertyBase = string(defaultValue) + protected fun property(defaultValue: String?): StoredPropertyBase = string(defaultValue) /** * Empty string is always normalized to null. */ - fun string(defaultValue: String? = null): StoredPropertyBase { + protected fun string(defaultValue: String? = null): StoredPropertyBase { val result = NormalizedStringStoredProperty(defaultValue) addProperty(result) return result } - fun property(defaultValue: Int = 0): StoredPropertyBase { + protected fun property(defaultValue: Int = 0): StoredPropertyBase { val result = IntStoredProperty(defaultValue, null) addProperty(result) return result } - fun property(defaultValue: Long = 0): StoredPropertyBase { + protected fun property(defaultValue: Long = 0): StoredPropertyBase { val result = LongStoredProperty(defaultValue, null) addProperty(result) return result } - fun property(defaultValue: Float = 0f, valueNormalizer: ((value: Float) -> Float)? = null): StoredPropertyBase { + protected fun property(defaultValue: Float = 0f, valueNormalizer: ((value: Float) -> Float)? = null): StoredPropertyBase { val result = FloatStoredProperty(defaultValue, valueNormalizer) addProperty(result) return result } - fun property(defaultValue: Boolean = false): StoredPropertyBase { + protected fun property(defaultValue: Boolean = false): StoredPropertyBase { val result = ObjectStoredProperty(defaultValue) addProperty(result) return result @@ -217,26 +231,8 @@ abstract class BaseState : SerializationFilter, ModificationTracker { } } - 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 { - append('"').append(value).append('"') - return this + // internal usage only + @Suppress("FunctionName") + @ApiStatus.Experimental + fun __getProperties() = properties } \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/xmlb/XmlSerializerImpl.java b/platform/util/src/com/intellij/util/xmlb/XmlSerializerImpl.java index cae26e096199..9eee32801287 100644 --- a/platform/util/src/com/intellij/util/xmlb/XmlSerializerImpl.java +++ b/platform/util/src/com/intellij/util/xmlb/XmlSerializerImpl.java @@ -255,20 +255,8 @@ public final class XmlSerializerImpl { accessor.setShort(host, Short.parseShort(value)); } else if (valueClass.isEnum()) { - Object deserializedValue = null; - for (Object enumConstant : valueClass.getEnumConstants()) { - if (enumConstant.toString().equals(value)) { - deserializedValue = enumConstant; - } - } - if (deserializedValue == null) { - for (Object enumConstant : valueClass.getEnumConstants()) { - if (enumConstant.toString().equalsIgnoreCase(value)) { - deserializedValue = enumConstant; - } - } - } - accessor.set(host, deserializedValue); + //noinspection unchecked + accessor.set(host, XmlSerializerUtil.stringToEnum(value, (Class>)valueClass, false)); } else if (Date.class.isAssignableFrom(valueClass)) { try { diff --git a/platform/util/src/com/intellij/util/xmlb/XmlSerializerUtil.java b/platform/util/src/com/intellij/util/xmlb/XmlSerializerUtil.java index 3638e21d2c57..2e3389c11d14 100644 --- a/platform/util/src/com/intellij/util/xmlb/XmlSerializerUtil.java +++ b/platform/util/src/com/intellij/util/xmlb/XmlSerializerUtil.java @@ -1,22 +1,9 @@ -/* - * Copyright 2000-2015 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. - */ +// 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.util.xmlb; import com.intellij.util.ReflectionUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; @@ -48,4 +35,22 @@ public class XmlSerializerUtil { public static List getAccessors(@NotNull Class aClass) { return BeanBinding.getAccessors(aClass); } + + @Nullable + public static Object stringToEnum(@NotNull String value, @NotNull Class> valueClass, boolean isAlwaysIgnoreCase) { + Enum[] enumConstants = valueClass.getEnumConstants(); + if (!isAlwaysIgnoreCase) { + for (Object enumConstant : enumConstants) { + if (enumConstant.toString().equals(value)) { + return enumConstant; + } + } + } + for (Object enumConstant : enumConstants) { + if (enumConstant.toString().equalsIgnoreCase(value)) { + return enumConstant; + } + } + return null; + } } diff --git a/plugins/configuration-script/src/com/intellij/configurationScript/JsonBuilder.kt b/plugins/configuration-script/src/com/intellij/configurationScript/JsonBuilder.kt index 643c11907669..20d1b6900813 100644 --- a/plugins/configuration-script/src/com/intellij/configurationScript/JsonBuilder.kt +++ b/plugins/configuration-script/src/com/intellij/configurationScript/JsonBuilder.kt @@ -76,16 +76,24 @@ internal class JsonObjectBuilder(private val builder: StringBuilder) { } fun rawMap(key: CharSequence, build: (StringBuilder) -> Unit) { + mapOrArray('{', '}', key, build) + } + + fun rawArray(key: CharSequence, build: (StringBuilder) -> Unit) { + mapOrArray('[', ']', key, build) + } + + private fun mapOrArray(openChar: Char, closeChar: Char, key: CharSequence, build: (StringBuilder) -> Unit) { builder .appendCommaIfNeed() .jsonEscapedString(key) .append(':') - .append('{') + .append(openChar) .append('\n') build(builder) builder .append('\n') - .append('}') + .append(closeChar) } fun rawBuilder(key: CharSequence, child: JsonObjectBuilder) { diff --git a/plugins/configuration-script/src/com/intellij/configurationScript/RunConfigurationJsonSchemaGenerator.kt b/plugins/configuration-script/src/com/intellij/configurationScript/RunConfigurationJsonSchemaGenerator.kt index f24eb43ddbf5..e6b063d4dd76 100644 --- a/plugins/configuration-script/src/com/intellij/configurationScript/RunConfigurationJsonSchemaGenerator.kt +++ b/plugins/configuration-script/src/com/intellij/configurationScript/RunConfigurationJsonSchemaGenerator.kt @@ -1,8 +1,9 @@ package com.intellij.configurationScript +import com.intellij.configurationStore.properties.EnumStoredProperty import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.configurations.ConfigurationType -import com.intellij.openapi.components.buildJsonSchema +import com.intellij.openapi.components.BaseState import com.intellij.openapi.diagnostic.debug import com.intellij.openapi.util.text.StringUtil import com.intellij.util.ReflectionUtil @@ -175,12 +176,38 @@ internal class RunConfigurationJsonSchemaGenerator { if (description != null) { "description" toUnescaped description } - rawMap("properties") { buildJsonSchema(state, it) } + map("properties") { buildJsonSchema(state, this) } } "additionalProperties" to false } } +// move buildJsonSchema and other such functions from BaseState to exclude from completion +private fun buildJsonSchema(state: BaseState, builder: JsonObjectBuilder) { + val properties = state.__getProperties() + // todo object definition + for (property in properties) { + builder.map(property.name!!) { + "type" to property.jsonType.jsonName + if (property is EnumStoredProperty<*>) { + describeEnum(property) + } + } + } +} + +private fun JsonObjectBuilder.describeEnum(property: EnumStoredProperty<*>) { + rawArray("enum") { stringBuilder -> + val enumConstants = property.clazz.enumConstants + for (enum in enumConstants) { + stringBuilder.append('"').append(enum.toString().toLowerCase()).append('"') + if (enum !== enumConstants.last()) { + stringBuilder.append(',') + } + } + } +} + // returns null if id is not valid internal fun rcTypeIdToPropertyName(configurationType: ConfigurationType): CharSequence? { val result = idToPropertyName(configurationType.tag, configurationType, null) ?: return null diff --git a/plugins/configuration-script/src/com/intellij/configurationScript/RunConfigurationListReader.kt b/plugins/configuration-script/src/com/intellij/configurationScript/RunConfigurationListReader.kt index a2eb95a1ffad..1ad015ad5a17 100644 --- a/plugins/configuration-script/src/com/intellij/configurationScript/RunConfigurationListReader.kt +++ b/plugins/configuration-script/src/com/intellij/configurationScript/RunConfigurationListReader.kt @@ -107,7 +107,7 @@ internal class RunConfigurationListReader(private val processor: (factory: Confi private fun readRc(optionsClass: Class, node: MappingNode, factory: ConfigurationFactory) { val state = ReflectionUtil.newInstance(optionsClass) - val properties = state.getProperties() + val properties = state.__getProperties() for (tuple in node.value) { val valueNode = tuple.valueNode val key = (tuple.keyNode as ScalarNode).value diff --git a/plugins/configuration-script/src/com/intellij/configurationScript/IntellijConfigurationAppInitializer.kt b/plugins/configuration-script/src/com/intellij/configurationScript/runConfigurationTemplateProvider.kt similarity index 100% rename from plugins/configuration-script/src/com/intellij/configurationScript/IntellijConfigurationAppInitializer.kt rename to plugins/configuration-script/src/com/intellij/configurationScript/runConfigurationTemplateProvider.kt diff --git a/plugins/configuration-script/test/ConfigurationFileTest.kt b/plugins/configuration-script/test/ConfigurationFileTest.kt index 2204f9568ef5..6559434353d8 100644 --- a/plugins/configuration-script/test/ConfigurationFileTest.kt +++ b/plugins/configuration-script/test/ConfigurationFileTest.kt @@ -119,7 +119,7 @@ class ConfigurationFileTest { } } -private fun parse(@Language("YAML") data: String, isTemplatesOnly: Boolean = false): List { +internal fun parse(@Language("YAML") data: String, isTemplatesOnly: Boolean = false): List { val list = SmartList() parseConfigurationFile(data.trimIndent().reader(), isTemplatesOnly) { _, state -> list.add(state) diff --git a/plugins/configuration-script/test/PropertyValueReaderTest.kt b/plugins/configuration-script/test/PropertyValueReaderTest.kt new file mode 100644 index 000000000000..c3f521c488d2 --- /dev/null +++ b/plugins/configuration-script/test/PropertyValueReaderTest.kt @@ -0,0 +1,28 @@ +package com.intellij.configurationScript + +import com.intellij.execution.ShortenCommandLine +import com.intellij.execution.application.JvmMainMethodRunConfigurationOptions +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.assertions.Assertions.assertThat +import org.junit.ClassRule +import org.junit.Test + +class PropertyValueReaderTest { + companion object { + @JvmField + @ClassRule + val projectRule = ProjectRule() + } + + @Test + fun `enum`() { + val result = parse(""" + runConfigurations: + jvmMainMethod: + shortenClasspath: MANIFEST + """) + val options = JvmMainMethodRunConfigurationOptions() + options.shortenClasspath = ShortenCommandLine.MANIFEST + assertThat(result).containsExactly(options) + } +} \ No newline at end of file