IJPL-155974 don't use ResourceBundle API for lookup (UI Designer don't use our Bundle, but JDK bundle, so, the only way to inject our transformation logic, is to override JDK bundle)

GitOrigin-RevId: cd3bda6cf13ce94afd82d3a3b34ff7d4b3f62937
This commit is contained in:
Vladimir Krivosheev
2024-06-24 12:39:53 +02:00
committed by intellij-monorepo-bot
parent d458fb9e65
commit 4b6e0d38a4
7 changed files with 225 additions and 87 deletions

View File

@@ -1,7 +1,7 @@
// 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.
package com.intellij.openapi.options.ex; package com.intellij.openapi.options.ex;
import com.intellij.BundleBase; import com.intellij.IntelliJResourceBundle;
import com.intellij.ide.actions.ConfigurablesPatcher; import com.intellij.ide.actions.ConfigurablesPatcher;
import com.intellij.ide.ui.search.SearchableOptionsRegistrar; import com.intellij.ide.ui.search.SearchableOptionsRegistrar;
import com.intellij.openapi.application.Application; import com.intellij.openapi.application.Application;
@@ -226,7 +226,7 @@ public final class ConfigurableExtensionPointUtil {
LOG.warn("Use <groupConfigurable> to specify custom configurable group: " + groupId); LOG.warn("Use <groupConfigurable> to specify custom configurable group: " + groupId);
int weight = getInt(bundle, id + ".settings.weight"); int weight = getInt(bundle, id + ".settings.weight");
String help = getString(bundle, id + ".settings.help.topic"); String help = getString(bundle, id + ".settings.help.topic");
String name = getName(bundle, id + ".settings.display.name"); String name = getString(bundle, id + ".settings.display.name");
String desc = getString(bundle, id + ".settings.description"); String desc = getString(bundle, id + ".settings.description");
if (name != null && project != null) { if (name != null && project != null) {
if (!project.isDefault() && !name.contains("{")) { if (!project.isDefault() && !name.contains("{")) {
@@ -237,7 +237,7 @@ public final class ConfigurableExtensionPointUtil {
name = StringUtil.first(MessageFormat.format(name, project.getName()), 30, true); name = StringUtil.first(MessageFormat.format(name, project.getName()), 30, true);
} }
} }
node.myValue = new SortedConfigurableGroup(id, name, desc, help, weight); node.myValue = new SortedConfigurableGroup(id, Objects.requireNonNullElse(name, ""), desc, help, weight);
} }
} }
if (configurables != null) { if (configurables != null) {
@@ -433,20 +433,17 @@ public final class ConfigurableExtensionPointUtil {
return null; return null;
} }
private static @Nls String getString(ResourceBundle bundle, @NonNls String resource) { private static @Nls @Nullable String getString(ResourceBundle bundle, @NonNls String resource) {
if (bundle == null) return null; if (bundle == null) {
try {
return bundle.getString(resource);
}
catch (MissingResourceException ignored) {
return null; return null;
} }
}
private static @Nls String getName(ResourceBundle bundle, @NonNls String resource) { if (bundle instanceof IntelliJResourceBundle b) {
if (bundle == null) return null; return b.getMessageOrNull(resource);
}
try { try {
return BundleBase.messageOrDefault(bundle, resource, null); return bundle.getString(resource);
} }
catch (MissingResourceException ignored) { catch (MissingResourceException ignored) {
return null; return null;

View File

@@ -31,7 +31,6 @@ f:com.intellij.BundleBase
- sf:partialMessage(java.util.ResourceBundle,java.lang.String,I,java.lang.Object[]):java.lang.String - sf:partialMessage(java.util.ResourceBundle,java.lang.String,I,java.lang.Object[]):java.lang.String
- sf:replaceMnemonicAmpersand(java.lang.String):java.lang.String - sf:replaceMnemonicAmpersand(java.lang.String):java.lang.String
- sf:setTranslationConsumer(java.util.function.BiConsumer):V - sf:setTranslationConsumer(java.util.function.BiConsumer):V
- sf:useDefaultValue(java.util.ResourceBundle,java.lang.String,java.lang.String):java.lang.String
f:com.intellij.Patches f:com.intellij.Patches
- sf:IBM_JDK_DISABLE_COLLECTION_BUG:Z - sf:IBM_JDK_DISABLE_COLLECTION_BUG:Z
- sf:JDK_BUG_ID_21275177:Z - sf:JDK_BUG_ID_21275177:Z

View File

@@ -4,7 +4,6 @@
package com.intellij package com.intellij
import com.intellij.BundleBase.partialMessage import com.intellij.BundleBase.partialMessage
import com.intellij.BundleBase.useDefaultValue
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.util.ArrayUtilRt import com.intellij.util.ArrayUtilRt
import com.intellij.util.DefaultBundleService import com.intellij.util.DefaultBundleService
@@ -54,14 +53,14 @@ open class AbstractBundle {
fun messageOrDefault(bundle: ResourceBundle?, key: @NonNls String, defaultValue: @Nls String?, vararg params: Any?): @Nls String? { fun messageOrDefault(bundle: ResourceBundle?, key: @NonNls String, defaultValue: @Nls String?, vararg params: Any?): @Nls String? {
return when { return when {
bundle == null -> defaultValue bundle == null -> defaultValue
!bundle.containsKey(key) -> postprocessValue(bundle = bundle, value = useDefaultValue(bundle, key, defaultValue), params = params) !bundle.containsKey(key) -> postprocessValue(bundle = bundle, value = defaultValue ?: useDefaultValue(bundle, key), params = params)
else -> BundleBase.messageOrDefault(bundle = bundle, key = key, defaultValue = defaultValue, params = params) else -> com.intellij.messageOrDefault(bundle = bundle, key = key, defaultValue = defaultValue, params = params)
} }
} }
@JvmStatic @JvmStatic
fun message(bundle: ResourceBundle, key: @NonNls String, vararg params: Any?): @Nls String { fun message(bundle: ResourceBundle, key: @NonNls String, vararg params: Any?): @Nls String {
return BundleBase.messageOrDefault(bundle = bundle, key = key, defaultValue = null, params = params) return com.intellij.messageOrDefault(bundle = bundle, key = key, defaultValue = null, params = params)
} }
@Suppress("HardCodedStringLiteral") @Suppress("HardCodedStringLiteral")
@@ -75,7 +74,7 @@ open class AbstractBundle {
inline fun resolveResourceBundleWithFallback( inline fun resolveResourceBundleWithFallback(
loader: ClassLoader, loader: ClassLoader,
pathToBundle: String, pathToBundle: String,
firstTry: () -> ResourceBundle firstTry: () -> ResourceBundle,
): ResourceBundle { ): ResourceBundle {
try { try {
return firstTry() return firstTry()
@@ -96,7 +95,7 @@ open class AbstractBundle {
@Contract(pure = true) @Contract(pure = true)
// open only to preserve compatibility // open only to preserve compatibility
open fun getMessage(key: @NonNls String, vararg params: Any?): @Nls String { open fun getMessage(key: @NonNls String, vararg params: Any?): @Nls String {
return BundleBase.messageOrDefault(bundle = resourceBundle, key = key, defaultValue = null, params = params) return com.intellij.messageOrDefault(bundle = resourceBundle, key = key, defaultValue = null, params = params)
} }
/** /**
@@ -202,7 +201,7 @@ private object IntelliJResourceControl : ResourceBundle.Control() {
val resourceName = (if (bundleName.contains("://")) null else toResourceName(bundleName, "properties")) ?: return null val resourceName = (if (bundleName.contains("://")) null else toResourceName(bundleName, "properties")) ?: return null
val stream = loader.getResourceAsStream(resourceName) ?: return null val stream = loader.getResourceAsStream(resourceName) ?: return null
return stream.use { return stream.use {
PropertyResourceBundle(InputStreamReader(it, StandardCharsets.UTF_8)) IntelliJResourceBundle(InputStreamReader(it, StandardCharsets.UTF_8))
} }
} }
} }

View File

@@ -1,6 +1,12 @@
// Copyright 2000-2024 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.
@file:Suppress("ReplaceGetOrSet")
package com.intellij package com.intellij
import com.intellij.BundleBase.L10N_MARKER
import com.intellij.BundleBase.SHOW_LOCALIZED_MESSAGES
import com.intellij.BundleBase.appendLocalizationSuffix
import com.intellij.BundleBase.getDefaultMessage
import com.intellij.BundleBase.replaceMnemonicAmpersand import com.intellij.BundleBase.replaceMnemonicAmpersand
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
@@ -25,15 +31,21 @@ private val SUFFIXES = arrayOf("</body></html>", "</html>")
private var translationConsumer: BiConsumer<String, String>? = null private var translationConsumer: BiConsumer<String, String>? = null
private val SHOW_DEFAULT_MESSAGES: Boolean = java.lang.Boolean.getBoolean("idea.l10n.english") private val SHOW_DEFAULT_MESSAGES: Boolean = java.lang.Boolean.getBoolean("idea.l10n.english")
// 0: no, 1: show key, 2: only keys
private val SHOW_KEYS: Int = System.getProperty("idea.l10n.keys").let { private val getPolicy: GetPolicy = System.getProperty("idea.l10n.keys").let {
when (it) { when (it) {
"true" -> 1 "true" -> GetPolicy.APPEND_KEY
"only" -> 2 "only" -> GetPolicy.ONLY_KEY
else -> 0 else -> GetPolicy.VALUE
} }
} }
private enum class GetPolicy {
VALUE,
APPEND_KEY,
ONLY_KEY,
}
object BundleBase { object BundleBase {
const val MNEMONIC: Char = 0x1B.toChar() const val MNEMONIC: Char = 0x1B.toChar()
const val MNEMONIC_STRING: @NlsSafe String = MNEMONIC.toString() const val MNEMONIC_STRING: @NlsSafe String = MNEMONIC.toString()
@@ -79,43 +91,17 @@ object BundleBase {
@JvmStatic @JvmStatic
fun message(bundle: ResourceBundle, key: String, vararg params: Any): @Nls String { fun message(bundle: ResourceBundle, key: String, vararg params: Any): @Nls String {
return messageOrDefault(bundle = bundle, key = key, defaultValue = null, params = params) return com.intellij.messageOrDefault(bundle = bundle, key = key, defaultValue = null, params = params)
} }
@Suppress("DuplicatedCode")
@JvmStatic @JvmStatic
fun messageOrDefault(bundle: ResourceBundle?, key: String, defaultValue: @Nls String?, vararg params: Any?): @Nls String { fun messageOrDefault(bundle: ResourceBundle?, key: String, defaultValue: @Nls String?, vararg params: Any?): @Nls String {
if (bundle == null) { if (bundle == null) {
return defaultValue!! return defaultValue!!
} }
var resourceFound = true return com.intellij.messageOrDefault(bundle = bundle, key = key, defaultValue = defaultValue, params = params)
val value = try {
bundle.getString(key)
}
catch (e: MissingResourceException) {
resourceFound = false
useDefaultValue(bundle = bundle, key = key, defaultValue = defaultValue)
}
if (SHOW_KEYS == 2) {
@Suppress("HardCodedStringLiteral")
return key
}
val result = postprocessValue(bundle = bundle, value = value, params = params)
translationConsumer?.accept(key, result)
return when {
!resourceFound -> result
SHOW_KEYS == 1 && SHOW_DEFAULT_MESSAGES -> {
appendLocalizationSuffix(result = result, suffixToAppend = " ($key=${getDefaultMessage(bundle, key)})")
}
SHOW_KEYS == 1 -> appendLocalizationSuffix(result = result, suffixToAppend = " ($key)")
SHOW_DEFAULT_MESSAGES -> {
appendLocalizationSuffix(result = result, suffixToAppend = " (${getDefaultMessage(bundle, key)})")
}
SHOW_LOCALIZED_MESSAGES -> appendLocalizationSuffix(result = result, suffixToAppend = L10N_MARKER)
else -> result
}
} }
@JvmStatic @JvmStatic
@@ -144,19 +130,6 @@ object BundleBase {
return result + suffixToAppend return result + suffixToAppend
} }
@JvmStatic
@Suppress("HardCodedStringLiteral")
fun useDefaultValue(bundle: ResourceBundle?, key: String, defaultValue: @Nls String?): @Nls String {
if (defaultValue != null) {
return defaultValue
}
if (assertOnMissedKeys) {
val bundleName = if (bundle == null) "" else "(${bundle.baseBundleName})"
LOG.error("'$key' is not found in $bundle$bundleName")
}
return "!$key!"
}
@JvmStatic @JvmStatic
@Contract(pure = true) @Contract(pure = true)
fun format(value: String, vararg params: Any): String { fun format(value: String, vararg params: Any): String {
@@ -244,20 +217,92 @@ private fun quotePattern(message: String): @NlsSafe String {
} }
@Suppress("HardCodedStringLiteral") @Suppress("HardCodedStringLiteral")
internal fun postprocessValue(bundle: ResourceBundle, value: @Nls String, vararg params: Any?): @Nls String { internal fun postprocessValue(bundle: ResourceBundle, value: @Nls String, params: Array<out Any?>?): @Nls String {
@Suppress("NAME_SHADOWING") var value = value @Suppress("NAME_SHADOWING") val value = replaceMnemonicAmpersand(value)!!
value = replaceMnemonicAmpersand(value)!! if (params.isNullOrEmpty() || !value.contains('{')) {
if (params.isEmpty() || !value.contains('{')) {
return value return value
} }
val locale = bundle.locale val locale = bundle.locale
return try { try {
val format = if (locale == null) MessageFormat(value) else MessageFormat(value, locale) val format = if (locale == null) MessageFormat(value) else MessageFormat(value, locale)
OrdinalFormat.apply(format) OrdinalFormat.apply(format)
format.format(params) return format.format(params)
} }
catch (e: IllegalArgumentException) { catch (e: IllegalArgumentException) {
"!invalid format: `$value`!" return "!invalid format: `$value`!"
}
}
@Suppress("DuplicatedCode")
@Internal
fun messageOrDefault(bundle: ResourceBundle, key: String, defaultValue: @Nls String?, params: Array<out Any?>?): @Nls String {
if (bundle !is IntelliJResourceBundle || bundle.parent != null) {
@Suppress("HardCodedStringLiteral")
return messageOrDefaultForJdkBundle(bundle = bundle, key = key, defaultValue = defaultValue, params = params)
}
return bundle.getMessage(key = key, defaultValue = defaultValue, params = params)
}
@Suppress("DuplicatedCode")
private fun messageOrDefaultForJdkBundle(
bundle: ResourceBundle,
key: String,
defaultValue: @Nls String?,
params: Array<out Any?>?,
): String {
var resourceFound = true
val value = try {
bundle.getString(key)
}
catch (e: MissingResourceException) {
resourceFound = false
defaultValue ?: useDefaultValue(bundle = bundle, key = key)
}
val result = postprocessValue(bundle = bundle, value = value, params = params)
translationConsumer?.accept(key, result)
return when {
!resourceFound -> result
getPolicy == GetPolicy.APPEND_KEY -> {
appendLocalizationSuffix(
result = result,
suffixToAppend = if (SHOW_DEFAULT_MESSAGES) " ($key=${getDefaultMessage(bundle, key)})" else " ($key)",
)
}
SHOW_DEFAULT_MESSAGES -> appendLocalizationSuffix(result = result, suffixToAppend = " (${getDefaultMessage(bundle, key)})")
SHOW_LOCALIZED_MESSAGES -> appendLocalizationSuffix(result = result, suffixToAppend = L10N_MARKER)
else -> result
}
}
internal fun useDefaultValue(bundle: ResourceBundle, @NlsSafe key: String): @Nls String {
if (assertOnMissedKeys) {
LOG.error("'$key' is not found (baseBundleName=${bundle.baseBundleName}, bundle=$bundle)")
}
return "!$key!"
}
internal fun postProcessResolvedValue(
@NlsSafe value: String,
key: String,
resourceFound: Boolean,
bundle: ResourceBundle,
): String {
translationConsumer?.accept(key, value)
return when {
!resourceFound -> value
getPolicy == GetPolicy.ONLY_KEY -> "$key${L10N_MARKER}$value"
getPolicy == GetPolicy.APPEND_KEY -> {
appendLocalizationSuffix(
result = value,
suffixToAppend = if (SHOW_DEFAULT_MESSAGES) " ($key=${getDefaultMessage(bundle, key)})" else " ($key)",
)
}
SHOW_DEFAULT_MESSAGES -> appendLocalizationSuffix(result = value,
suffixToAppend = " (${getDefaultMessage(bundle, key)})")
SHOW_LOCALIZED_MESSAGES -> appendLocalizationSuffix(result = value, suffixToAppend = L10N_MARKER)
else -> value
} }
} }

View File

@@ -0,0 +1,99 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("ReplaceGetOrSet")
package com.intellij
import com.intellij.openapi.util.NlsSafe
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import java.io.Reader
import java.util.*
@ApiStatus.Internal
class IntelliJResourceBundle internal constructor(reader: Reader) : ResourceBundle() {
private val lookup: Map<String, String>
init {
val properties = Properties()
properties.load(reader)
// don't use immutable map - HashMap performance is better (with String keys)
@Suppress("UNCHECKED_CAST")
lookup = HashMap(properties as Map<String, String>)
}
val parent: ResourceBundle?
get() = super.parent
@NlsSafe
fun getMessageOrNull(key: String): String? {
val value = lookup.get(key) ?: return null
return postProcessResolvedValue(value = value, key = key, resourceFound = true, bundle = this)
}
@NlsSafe
fun getMessage(key: String, defaultValue: @Nls String?, params: Array<out Any?>?): String {
@Suppress("HardCodedStringLiteral")
var value = lookup.get(key)
val resourceFound = if (value == null) {
value = defaultValue ?: useDefaultValue(bundle = this, key = key)
false
}
else {
true
}
return postProcessResolvedValue(
value = postprocessValue(bundle = this, value = value, params = params),
key = key,
resourceFound = resourceFound,
bundle = this,
)
}
// UI Designer uses ResourceBundle directly, via Java API.
// `getMessage` is not called, so we have to provide our own implementation of ResourceBundle
override fun handleGetObject(key: String): String? = getMessageOrNull(key = key)
override fun getKeys(): Enumeration<String> {
val parent = super.parent
return if (parent == null) Collections.enumeration(lookup.keys) else ResourceBundleWithParentEnumeration(lookup.keys, parent.keys)
}
override fun handleKeySet(): Set<String> = lookup.keys
}
private class ResourceBundleWithParentEnumeration(
private var set: Set<String?>,
private var enumeration: Enumeration<String>,
) : Enumeration<String> {
private var iterator: Iterator<String?> = set.iterator()
private var next: String? = null
override fun hasMoreElements(): Boolean {
if (next == null) {
if (iterator.hasNext()) {
next = iterator.next()
}
else {
while (next == null && enumeration.hasMoreElements()) {
next = enumeration.nextElement()
if (set.contains(next)) {
next = null
}
}
}
}
return next != null
}
override fun nextElement(): String? {
if (hasMoreElements()) {
val result = next
next = null
return result
}
else {
throw NoSuchElementException()
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2020 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-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij; package com.intellij;
import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.Nls;
@@ -14,7 +14,7 @@ public final class UtilBundle {
private UtilBundle() { } private UtilBundle() { }
public static @NotNull @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) { public static @NotNull @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
return BundleBase.messageOrDefault(getUtilBundle(), key, null, params); return BundleBaseKt.messageOrDefault(getUtilBundle(), key, null, params);
} }
private static @NotNull ResourceBundle getUtilBundle() { private static @NotNull ResourceBundle getUtilBundle() {

View File

@@ -1,4 +1,4 @@
// 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.
package org.editorconfig package org.editorconfig
import com.intellij.BundleBase import com.intellij.BundleBase
@@ -22,11 +22,7 @@ import com.intellij.psi.search.FileTypeIndex
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.LineSeparator import com.intellij.util.LineSeparator
import org.ec4j.core.ResourceProperties import org.ec4j.core.ResourceProperties
import org.editorconfig.configmanagement.ConfigEncodingCharsetUtil import org.editorconfig.configmanagement.*
import org.editorconfig.configmanagement.StandardEditorConfigProperties
import org.editorconfig.configmanagement.indentSizeKey
import org.editorconfig.configmanagement.indentStyleKey
import org.editorconfig.configmanagement.tabWidthKey
import org.editorconfig.language.messages.EditorConfigBundle import org.editorconfig.language.messages.EditorConfigBundle
import org.editorconfig.plugincomponents.EditorConfigPropertiesService import org.editorconfig.plugincomponents.EditorConfigPropertiesService
import org.editorconfig.settings.EditorConfigSettings import org.editorconfig.settings.EditorConfigSettings
@@ -81,9 +77,12 @@ object Utils {
fun invalidConfigMessage(project: Project, configValue: String?, configKey: String, filePath: String?) { fun invalidConfigMessage(project: Project, configValue: String?, configKey: String, filePath: String?) {
val message = if (configValue != null) { val message = if (configValue != null) {
BundleBase.messageOrDefault(EditorConfigBundle.bundle.resourceBundle, "invalid.config.value", BundleBase.messageOrDefault(EditorConfigBundle.bundle.resourceBundle,
"invalid.config.value",
null, null,
configValue, configKey.ifEmpty { "?" }, filePath) configValue,
configKey.ifEmpty { "?" },
filePath)
} }
else { else {
EditorConfigBundle.message("read.failure") EditorConfigBundle.message("read.failure")