IJPL-736 ability to provide local value to save on set (second and final part)

GitOrigin-RevId: a5ee7654ad41a6cd16b3aa5702288894a433b190
This commit is contained in:
Vladimir Krivosheev
2024-03-08 07:55:59 +00:00
committed by intellij-monorepo-bot
parent 58f7042ae0
commit a628cbd5e8
11 changed files with 126 additions and 28 deletions
@@ -244,19 +244,28 @@ private fun serializeWithController(
val isPropertySkipped = isPropertySkipped(filter = filter, binding = binding, bean = state, rootBinding = rootBinding, isFilterPropertyItself = true)
val key = SettingDescriptor(key = createSettingKey(componentName, binding), pluginId = pluginId, tags = keyTags, serializer = JsonElementSettingSerializerDescriptor)
val result = controller.doSetItem(key = key, value = if (isPropertySkipped) null else binding.toJson(state, filter))
if (result != SetResult.inapplicable()) {
if (isPropertySkipped) {
continue
}
if (isPropertySkipped) {
continue
var effectiveState = state
if (result != SetResult.inapplicable()) {
val value = result.value
if (value is JsonElement) {
// substituted value
effectiveState = rootBinding.newInstance()
binding.setFromJson(effectiveState, value)
}
else {
continue
}
}
if (element == null) {
element = Element(rootBinding.tagName)
}
binding.serialize(bean = state, parent = element, filter = filter)
binding.serialize(bean = effectiveState, parent = element, filter = filter)
}
return element
}
@@ -367,6 +367,55 @@ class ControllerBackedStoreTest {
assertThat(Json.encodeToString(saved)).isEqualTo("""{"foo":"newValue","bar":"test2"}""")
}
@Suppress("unused")
@Test
fun substitute_value_on_set() = runBlocking<Unit>(Dispatchers.Default) {
data class TestState(var foo: String = "", var bar: String = "")
val store = createStore(object : DelegatedSettingsController {
override fun <T : Any> getItem(key: SettingDescriptor<T>): GetResult<T?> = GetResult.inapplicable()
override fun <T : Any> setItem(key: SettingDescriptor<T>, value: T?): SetResult {
if (key.key == "TestState.foo") {
return SetResult.substituted(JsonPrimitive("overridden"))
}
else {
return SetResult.inapplicable()
}
}
})
@State(name = "TestState", storages = [Storage(value = TEST_COMPONENT_FILE_NAME)], allowLoadInTests = true)
class TestComponent : SerializablePersistentStateComponent<TestState>(TestState()) {
override fun noStateLoaded() {
loadState(TestState())
}
}
val component = TestComponent()
store.initComponent(component = component, serviceDescriptor = null, pluginId = PluginManagerCore.CORE_ID)
assertThat(component.state.foo).isEmpty()
component.state.foo = "newValue"
store.save(forceSavingAllSettings = true)
assertThat(component.state.foo).isEqualTo("newValue")
// but the new value on disk
assertThat(JDOMUtil.load(appConfig.resolve(TEST_COMPONENT_FILE_NAME))).isEqualTo("""
<application>
<component name="TestState">
<option name="foo" value="overridden" />
</component>
</application>
""".trimIndent())
store.reloadStates(setOf("TestState"))
// and after reload, the new value is applied to the component
assertThat(component.state.foo).isEqualTo("overridden")
}
@Suppress("unused")
@Test
fun cache_storage() = runBlocking<Unit>(Dispatchers.Default) {
@@ -51,7 +51,7 @@ class SettingsControllerMediator(
var totalResult = SetResult.inapplicable()
for (controller in controllers) {
val result = controller.setItem(key = key, value = value)
if (result == SetResult.forbid()) {
if (result == SetResult.forbid() || result.value !is Enum<*>) {
return result
}
else if (result == SetResult.done()) {
+3
View File
@@ -0,0 +1,3 @@
[*]
# critical subsystem - named params used a lot
max_line_length = 180
+2 -4
View File
@@ -1,5 +1,3 @@
FROM nginxinc/nginx-unprivileged
COPY --chown=101:101 site /usr/share/nginx/html
FROM nginxinc/nginx-unprivileged:1.25.4
COPY --chown=101:101 nginx.conf /etc/nginx/conf.d/default.conf
# overview.html as index file for nginx
COPY --chown=101:101 site /usr/share/nginx/html
+23 -5
View File
@@ -1,14 +1,14 @@
#!/usr/bin/env bash
# Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
# Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
set -e -u -x
BASEDIR=$(dirname "$0")
cd "$BASEDIR/../../../../out" || exit
rm -rf settings-doc-draf/
mkdir -p settings-doc-draf/site
rm -rf settings-doc-draft/
mkdir -p settings-doc-draft/site
cd settings-doc-draf
cd settings-doc-draft
unzip ../webHelpSETTINGS-DOCS-DRAFT2-all.zip -d site
cp "$BASEDIR"/Dockerfile ./
@@ -17,4 +17,22 @@ cp "$BASEDIR"/nginx.conf ./
docker build --platform=linux/amd64 . -t registry.jetbrains.team/p/ij/structurizr/settings-docs:latest
docker push registry.jetbrains.team/p/ij/structurizr/settings-docs:latest
echo "$PWD"
echo "$PWD"
# docker doesn't have D2
#OUT_DIR="$BASEDIR/../../../../out/settings-doc-draft"
#rm -rf "$OUT_DIR"
#mkdir -p "$OUT_DIR"
#
## see latest version in https://jetbrains.team/p/writerside/packages/container/builder/writerside-builder
#docker run --rm -v .:/opt/sources -v "$OUT_DIR":/opt/out registry.jetbrains.team/p/writerside/builder/writerside-builder:2.1.1755-p5358 /bin/bash -c "
#export DISPLAY=:99 &&
#Xvfb :99 &
#/opt/builder/bin/idea.sh helpbuilderinspect \
#--source-dir /opt/sources \
#--product Writerside/settings-docs-draft \
#--runner other \
#--output-dir /opt/out
#"
#
#cd "$OUT_DIR"
+2 -8
View File
@@ -3,8 +3,8 @@ server {
absolute_redirect off;
rewrite ^(/.*)\.html(\?.*)?$ $1$2 permanent;
rewrite ^/(.*)/$ /$1 permanent;
rewrite ^(/.*)\.html(\?.*)?$ $1$2 redirect;
rewrite ^/(.*)/$ /$1 redirect;
root /usr/share/nginx/html;
@@ -15,10 +15,4 @@ server {
location = / {
return 302 overview;
}
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
@@ -10,4 +10,5 @@
<toc-element topic="setting-descriptor.md"/>
<toc-element topic="setting-types.md"/>
<toc-element topic="bridge-to-old-api.md"/>
<toc-element topic="storage-design.md"/>
</instance-profile>
@@ -1,17 +1,20 @@
# Implicit support for PersistenceStateComponent
# Seamless Support for PersistenceStateComponent
The new Settings Controller API is currently not intended for use by end clients.
All existing implementations of `PersistenceStateComponent` that don't use the deprecated API like `JDOMExternalizable` are fully supported, and no changes are required.
All existing implementations of `PersistenceStateComponent` that don't use the deprecated API `JDOMExternalizable` are fully supported, and no changes are required.
Support here means that each component property serves as a key, not the whole component.
## Unified Format
The Settings Controller operates with values in a unified format, where each value is a [JsonElement](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/).
Please note that the corresponding API is advanced and experimental, and the `JsonElement` API may be changed in the future.
For primitive values (no binding or binding plus converter, meaning effectively is a string): `"a string"`, `number`, `boolean`.
In other words, a string is quoted with double quotes and other primitive values as is (JSON syntax).
For complex values (binding exists), like
* map (`MapBinding`),
* collection (`AbstractCollectionBinding` — `ArrayBinding` or `CollectionBinding`),
* collection (`CollectionBinding`),
* bean (`BeanBinding` and `KotlinAwareBeanBinding`, about `KotlinxSerializationBinding` see note about custom bindings),
also a JSON syntax is used.
@@ -40,7 +43,7 @@ If a map has a complex key, then it is an array of objects, with each object rep
```
Maps with non-primitive keys are expected to be rare.
Currently, `SettingDescriptor` lacks tags for distinguishing formats or providing format details.
Currently, `SettingDescriptor` lacks tags which could be used to distinguish formats or provide format details.
These could be added if necessary.
### Collections
@@ -107,4 +110,4 @@ In a nutshell:
* `BeanBinding.deserializeInto` to deserialize.
Please note that this API is internal and low-level. It requires a profound understanding of the IntelliJ Platform. It may not be as straightforward as it sounds.
Therefore, its use outside of the IJ Platform is strongly discouraged.
Therefore, its use outside the IJ Platform is strongly discouraged.
@@ -0,0 +1,18 @@
# Storage Design
Please note these are technical notes, not end user documentation.
## StateStorageBackedByController
`StateStorageBackedByController` is a special implementation of `StateStorage`.
It's another PSC bridge implementation and currently, it's only used for cache (`StoragePathMacros.CACHE_FILE`).
You might wonder why it's not used for [internal](setting-types.md) settings. Well, this is because it lacks numerous features like support for `StreamProvider` (settings sync, settings import). While we could potentially overcome this, the return on investment isn't evident for now.
So, how does it work? It utilizes the same [unified format](bridge-to-old-api.md#unified-format) and also employs `JsonElementSettingSerializerDescriptor` as the setting key serializator.
Although the implementation shares some similarities with the PSC bridge, it's not completely identical. As `StateStorageBackedByController` is an exclusive storage, we don't need to handle local data (e.g., merging).
Currently, `LocalSettingsController` only supports keys with `CacheTag` and `NonShareableInternalTag` tags.
It uses [MVStore](https://www.h2database.com/html/mvstore.html) to store data on the disk. Specifically, we use `MVMap<String, ByteArray>` — where the setting key is transformed into a string (plugin id + setting key). Any type of value is serialized into CBOR. This allows us to deserialize the value later without knowing the data type - whether it's a string, number, etc. As of now, this feature is utilized by `DumpDevIdeaCacheDb` to dump the MVStore database into YAML.
For `JsonElementSettingSerializerDescriptor`, where the serializer cannot be used for any format beyond JSON, we encode the JSON element into a string, and subsequently encode it to CBOR. So, the value gets stored as a CBOR string.
@@ -3,6 +3,8 @@ package com.intellij.platform.settings
import com.intellij.openapi.components.ComponentManager
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.intellij.util.xmlb.SettingsInternalApi
import kotlinx.serialization.json.JsonElement
import org.jetbrains.annotations.ApiStatus.*
import java.nio.file.Path
import java.util.*
@@ -61,15 +63,18 @@ value class GetResult<out T : Any?> @PublishedApi internal constructor(@Publishe
@Internal
@JvmInline
value class SetResult @PublishedApi internal constructor(@PublishedApi internal val value: Any?) {
value class SetResult @PublishedApi internal constructor(@Internal @SettingsInternalApi val value: Any?) {
companion object {
fun inapplicable(): SetResult = SetResult(SetResultResolution.INAPPLICABLE)
fun forbid(): SetResult = SetResult(SetResultResolution.FORBID)
fun done(): SetResult = SetResult(SetResultResolution.DONE)
fun substituted(value: JsonElement): SetResult = SetResult(value)
}
@OptIn(SettingsInternalApi::class)
override fun toString(): String = "SetResult($value)"
}