get rid of manual XML read/write — ShortenCommandLine.readShortenClasspathMethod, support enum in DSL (schema and read)

This commit is contained in:
Vladimir Krivosheev
2018-09-06 16:10:53 +02:00
parent e86a9f810d
commit ae8e849800
17 changed files with 196 additions and 105 deletions
@@ -61,7 +61,7 @@ public abstract class JavaTestConfigurationBase extends ModuleBasedConfiguration
}
@Override
public void setShortenCommandLine(ShortenCommandLine shortenCommandLine) {
public void setShortenCommandLine(@Nullable ShortenCommandLine shortenCommandLine) {
myShortenCommandLine = shortenCommandLine;
}
@@ -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<JavaRunCo
RefactoringListenerProvider, InputRedirectAware {
/* deprecated, but 3rd-party used variables */
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated public String MAIN_CLASS_NAME;
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated public String PROGRAM_PARAMETERS;
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated public String WORKING_DIRECTORY;
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated public boolean ALTERNATIVE_JRE_PATH_ENABLED;
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated public String ALTERNATIVE_JRE_PATH;
/* */
private ShortenCommandLine myShortenCommandLine = null;
private final InputRedirectAware.InputRedirectOptions myInputRedirectOptions = new InputRedirectOptions();
public ApplicationConfiguration(String name, @NotNull Project project, @NotNull ApplicationConfigurationType configurationType) {
@@ -275,11 +280,19 @@ public class ApplicationConfiguration extends ModuleBasedConfiguration<JavaRunCo
return JavaRunConfigurationModule.getModulesForClass(getProject(), getMainClassName());
}
@SuppressWarnings("deprecation")
@Override
public void readExternal(@NotNull final Element element) {
super.readExternal(element);
syncOldStateFields();
JavaRunConfigurationExtensionManager.getInstance().readExternal(this, element);
setShortenCommandLine(ShortenCommandLine.readShortenClasspathMethod(element));
myInputRedirectOptions.readExternal(element);
}
@SuppressWarnings("deprecation")
private void syncOldStateFields() {
JvmMainMethodRunConfigurationOptions options = getOptions();
String workingDirectory = options.getWorkingDirectory();
@@ -295,10 +308,12 @@ public class ApplicationConfiguration extends ModuleBasedConfiguration<JavaRunCo
WORKING_DIRECTORY = workingDirectory;
ALTERNATIVE_JRE_PATH = options.getAlternativeJrePath();
ALTERNATIVE_JRE_PATH_ENABLED = options.isAlternativeJrePathEnabled();
}
JavaRunConfigurationExtensionManager.getInstance().readExternal(this, element);
setShortenCommandLine(ShortenCommandLine.readShortenClasspathMethod(element));
myInputRedirectOptions.readExternal(element);
@Override
public void setState(@NotNull BaseState state) {
super.setState(state);
syncOldStateFields();
}
@Override
@@ -306,19 +321,18 @@ public class ApplicationConfiguration extends ModuleBasedConfiguration<JavaRunCo
super.writeExternal(element);
JavaRunConfigurationExtensionManager.getInstance().writeExternal(this, element);
ShortenCommandLine.writeShortenClasspathMethod(element, myShortenCommandLine);
myInputRedirectOptions.writeExternal(element);
}
@Nullable
@Override
public ShortenCommandLine getShortenCommandLine() {
return myShortenCommandLine;
return getOptions().getShortenClasspath();
}
@Override
public void setShortenCommandLine(ShortenCommandLine mode) {
myShortenCommandLine = mode;
public void setShortenCommandLine(@Nullable ShortenCommandLine mode) {
getOptions().setShortenClasspath(mode);
}
@NotNull
@@ -2,26 +2,32 @@
package com.intellij.execution.application
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")
open var programParameters: String? by string()
open var programParameters by string()
@get:OptionTag("WORKING_DIRECTORY")
open var workingDirectory: String? by string()
open var workingDirectory by string()
@get:OptionTag("INCLUDE_PROVIDED_SCOPE")
var includeProvidedScope: Boolean by property(false)
var includeProvidedScope by property(false)
@get:OptionTag("ENABLE_SWING_INSPECTOR")
var isSwingInspectorEnabled: Boolean by property(false)
var isSwingInspectorEnabled by property(false)
@get:OptionTag("PASS_PARENT_ENVS")
var isPassParentEnv: Boolean by property(true)
var isPassParentEnv by property(true)
@get:XMap(propertyElementName = "envs", entryTagName = "env", keyAttributeName = "name")
var env: MutableMap<String, String> 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<ShortenCommandLine>()
}
@@ -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);
}
@@ -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()));
@@ -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;
}
@@ -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();
}
@@ -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<T>(protected var value: T) : StoredPropertyBase<T>() {
abstract class ObjectStateStoredPropertyBase<T>(protected var value: T) : StoredPropertyBase<T>() {
override val jsonType: JsonSchemaType
get() = JsonSchemaType.OBJECT
@@ -55,6 +56,33 @@ internal open class ObjectStoredProperty<T>(private val defaultValue: T) : Objec
}
}
class EnumStoredProperty<T : Enum<*>>(private val defaultValue: T?, val clazz: Class<T>) : ObjectStateStoredPropertyBase<T?>(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<T : BaseState?>(initialValue: T) : ObjectStateStoredPropertyBase<T>(initialValue) {
override fun isEqualToDefault(): Boolean {
val value = value
@@ -32,7 +32,7 @@ abstract class BaseState : SerializationFilter, ModificationTracker {
properties.add(p as StoredProperty<Any>)
}
fun <T> property(): StoredPropertyBase<T?> {
protected fun <T> property(): StoredPropertyBase<T?> {
val result = ObjectStoredProperty<T?>(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 <T : BaseState?> property(initialValue: T): StoredPropertyBase<T> {
protected fun <T : BaseState?> property(initialValue: T): StoredPropertyBase<T> {
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 <T> property(initialValue: T, isDefault: (value: T) -> Boolean): StoredPropertyBase<T> {
protected fun <T> property(initialValue: T, isDefault: (value: T) -> Boolean): StoredPropertyBase<T> {
val result = object : ObjectStoredProperty<T>(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 <E, C : MutableCollection<E>> property(initialValue: C): StoredPropertyBase<C> {
protected fun <E, C : MutableCollection<E>> property(initialValue: C): StoredPropertyBase<C> {
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 <T : Charset> property(initialValue: T): StoredPropertyBase<T> {
protected fun <T : Charset> property(initialValue: T): StoredPropertyBase<T> {
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 <T : Enum<*>> property(defaultValue: T): StoredPropertyBase<T> {
protected fun <T : Enum<*>> property(defaultValue: T): StoredPropertyBase<T> {
val result = ObjectStoredProperty(defaultValue)
addProperty(result)
return result
}
/**
* `null` is always normalized to null.
*/
protected inline fun <reified T : Enum<*>> enum(defaultValue: T? = null): StoredPropertyBase<T?> {
return doEnum(defaultValue, T::class.java)
}
@PublishedApi
internal fun <T : Enum<*>> doEnum(defaultValue: T? = null, clazz: Class<T>): StoredPropertyBase<T?> {
val result = EnumStoredProperty(defaultValue, clazz)
addProperty(result)
return result
}
/**
* Not-null list. Initialized as SmartList.
*/
fun <T : Any> list(): StoredPropertyBase<MutableList<T>> {
protected fun <T : Any> list(): StoredPropertyBase<MutableList<T>> {
val result = ListStoredProperty<T>()
addProperty(result)
@Suppress("UNCHECKED_CAST")
return result as StoredPropertyBase<MutableList<T>>
}
fun <K : Any, V: Any> property(value: MutableMap<K, V>): StoredPropertyBase<MutableMap<K, V>> {
protected fun <K : Any, V: Any> property(value: MutableMap<K, V>): StoredPropertyBase<MutableMap<K, V>> {
return map(value)
}
fun <K : Any, V: Any> map(value: MutableMap<K, V> = THashMap()): StoredPropertyBase<MutableMap<K, V>> {
protected fun <K : Any, V: Any> map(value: MutableMap<K, V> = THashMap()): StoredPropertyBase<MutableMap<K, V>> {
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?> = string(defaultValue)
protected fun property(defaultValue: String?): StoredPropertyBase<String?> = string(defaultValue)
/**
* Empty string is always normalized to null.
*/
fun string(defaultValue: String? = null): StoredPropertyBase<String?> {
protected fun string(defaultValue: String? = null): StoredPropertyBase<String?> {
val result = NormalizedStringStoredProperty(defaultValue)
addProperty(result)
return result
}
fun property(defaultValue: Int = 0): StoredPropertyBase<Int> {
protected fun property(defaultValue: Int = 0): StoredPropertyBase<Int> {
val result = IntStoredProperty(defaultValue, null)
addProperty(result)
return result
}
fun property(defaultValue: Long = 0): StoredPropertyBase<Long> {
protected fun property(defaultValue: Long = 0): StoredPropertyBase<Long> {
val result = LongStoredProperty(defaultValue, null)
addProperty(result)
return result
}
fun property(defaultValue: Float = 0f, valueNormalizer: ((value: Float) -> Float)? = null): StoredPropertyBase<Float> {
protected fun property(defaultValue: Float = 0f, valueNormalizer: ((value: Float) -> Float)? = null): StoredPropertyBase<Float> {
val result = FloatStoredProperty(defaultValue, valueNormalizer)
addProperty(result)
return result
}
fun property(defaultValue: Boolean = false): StoredPropertyBase<Boolean> {
protected fun property(defaultValue: Boolean = false): StoredPropertyBase<Boolean> {
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
}
@@ -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<? extends Enum<?>>)valueClass, false));
}
else if (Date.class.isAssignableFrom(valueClass)) {
try {
@@ -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<MutableAccessor> getAccessors(@NotNull Class<?> aClass) {
return BeanBinding.getAccessors(aClass);
}
@Nullable
public static Object stringToEnum(@NotNull String value, @NotNull Class<? extends Enum<?>> 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;
}
}
@@ -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) {
@@ -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
@@ -107,7 +107,7 @@ internal class RunConfigurationListReader(private val processor: (factory: Confi
private fun readRc(optionsClass: Class<out BaseState>, 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
@@ -119,7 +119,7 @@ class ConfigurationFileTest {
}
}
private fun parse(@Language("YAML") data: String, isTemplatesOnly: Boolean = false): List<Any> {
internal fun parse(@Language("YAML") data: String, isTemplatesOnly: Boolean = false): List<Any> {
val list = SmartList<Any>()
parseConfigurationFile(data.trimIndent().reader(), isTemplatesOnly) { _, state ->
list.add(state)
@@ -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)
}
}