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;
import com.intellij.BundleBase;
import com.intellij.IntelliJResourceBundle;
import com.intellij.ide.actions.ConfigurablesPatcher;
import com.intellij.ide.ui.search.SearchableOptionsRegistrar;
import com.intellij.openapi.application.Application;
@@ -226,7 +226,7 @@ public final class ConfigurableExtensionPointUtil {
LOG.warn("Use <groupConfigurable> to specify custom configurable group: " + groupId);
int weight = getInt(bundle, id + ".settings.weight");
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");
if (name != null && project != null) {
if (!project.isDefault() && !name.contains("{")) {
@@ -237,7 +237,7 @@ public final class ConfigurableExtensionPointUtil {
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) {
@@ -433,20 +433,17 @@ public final class ConfigurableExtensionPointUtil {
return null;
}
private static @Nls String getString(ResourceBundle bundle, @NonNls String resource) {
if (bundle == null) return null;
try {
return bundle.getString(resource);
}
catch (MissingResourceException ignored) {
private static @Nls @Nullable String getString(ResourceBundle bundle, @NonNls String resource) {
if (bundle == null) {
return null;
}
}
private static @Nls String getName(ResourceBundle bundle, @NonNls String resource) {
if (bundle == null) return null;
if (bundle instanceof IntelliJResourceBundle b) {
return b.getMessageOrNull(resource);
}
try {
return BundleBase.messageOrDefault(bundle, resource, null);
return bundle.getString(resource);
}
catch (MissingResourceException ignored) {
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:replaceMnemonicAmpersand(java.lang.String):java.lang.String
- 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
- sf:IBM_JDK_DISABLE_COLLECTION_BUG:Z
- sf:JDK_BUG_ID_21275177:Z

View File

@@ -4,7 +4,6 @@
package com.intellij
import com.intellij.BundleBase.partialMessage
import com.intellij.BundleBase.useDefaultValue
import com.intellij.openapi.diagnostic.logger
import com.intellij.util.ArrayUtilRt
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? {
return when {
bundle == null -> defaultValue
!bundle.containsKey(key) -> postprocessValue(bundle = bundle, value = useDefaultValue(bundle, key, defaultValue), params = params)
else -> BundleBase.messageOrDefault(bundle = bundle, key = key, defaultValue = defaultValue, params = params)
!bundle.containsKey(key) -> postprocessValue(bundle = bundle, value = defaultValue ?: useDefaultValue(bundle, key), params = params)
else -> com.intellij.messageOrDefault(bundle = bundle, key = key, defaultValue = defaultValue, params = params)
}
}
@JvmStatic
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")
@@ -75,7 +74,7 @@ open class AbstractBundle {
inline fun resolveResourceBundleWithFallback(
loader: ClassLoader,
pathToBundle: String,
firstTry: () -> ResourceBundle
firstTry: () -> ResourceBundle,
): ResourceBundle {
try {
return firstTry()
@@ -96,7 +95,7 @@ open class AbstractBundle {
@Contract(pure = true)
// open only to preserve compatibility
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 stream = loader.getResourceAsStream(resourceName) ?: return null
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.
@file:Suppress("ReplaceGetOrSet")
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.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 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) {
"true" -> 1
"only" -> 2
else -> 0
"true" -> GetPolicy.APPEND_KEY
"only" -> GetPolicy.ONLY_KEY
else -> GetPolicy.VALUE
}
}
private enum class GetPolicy {
VALUE,
APPEND_KEY,
ONLY_KEY,
}
object BundleBase {
const val MNEMONIC: Char = 0x1B.toChar()
const val MNEMONIC_STRING: @NlsSafe String = MNEMONIC.toString()
@@ -79,43 +91,17 @@ object BundleBase {
@JvmStatic
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
fun messageOrDefault(bundle: ResourceBundle?, key: String, defaultValue: @Nls String?, vararg params: Any?): @Nls String {
if (bundle == null) {
return defaultValue!!
}
var resourceFound = true
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
}
return com.intellij.messageOrDefault(bundle = bundle, key = key, defaultValue = defaultValue, params = params)
}
@JvmStatic
@@ -144,19 +130,6 @@ object BundleBase {
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
@Contract(pure = true)
fun format(value: String, vararg params: Any): String {
@@ -244,20 +217,92 @@ private fun quotePattern(message: String): @NlsSafe String {
}
@Suppress("HardCodedStringLiteral")
internal fun postprocessValue(bundle: ResourceBundle, value: @Nls String, vararg params: Any?): @Nls String {
@Suppress("NAME_SHADOWING") var value = value
value = replaceMnemonicAmpersand(value)!!
if (params.isEmpty() || !value.contains('{')) {
internal fun postprocessValue(bundle: ResourceBundle, value: @Nls String, params: Array<out Any?>?): @Nls String {
@Suppress("NAME_SHADOWING") val value = replaceMnemonicAmpersand(value)!!
if (params.isNullOrEmpty() || !value.contains('{')) {
return value
}
val locale = bundle.locale
return try {
try {
val format = if (locale == null) MessageFormat(value) else MessageFormat(value, locale)
OrdinalFormat.apply(format)
format.format(params)
return format.format(params)
}
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;
import org.jetbrains.annotations.Nls;
@@ -14,7 +14,7 @@ public final class UtilBundle {
private UtilBundle() { }
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() {

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