Skip to content

Commit 46cf52b

Browse files
committed
feat: add ResourceKeyed, LocalizedValue interfaces for combobox localization
1 parent 1f17592 commit 46cf52b

8 files changed

Lines changed: 276 additions & 18 deletions

File tree

build-logic/verification/src/main/kotlin/build-logic.autostyle.gradle.kts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,12 @@ plugins.withId("org.jetbrains.kotlin.jvm") {
101101
kotlin {
102102
license()
103103
trimTrailingWhitespace()
104-
ktlint("0.40.0")
104+
ktlint("0.40.0") {
105+
filter {
106+
// TODO: remove exclusion when update ktlint
107+
exclude("**/org/apache/jorphan/locale/PlainValue.kt")
108+
}
109+
}
105110
endWithNewline()
106111
}
107112
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.jorphan.locale
19+
20+
import org.apiguardian.api.API
21+
22+
/**
23+
* Marker interface for values that can be used in a combo box.
24+
*
25+
* This interface is intended to standardize handling of values for GUI components
26+
* like combo boxes. Classes implementing this interface can define how their
27+
* values are represented and localized, providing flexibility for different types
28+
* of combo box content.
29+
*
30+
* Implementers may include:
31+
* - Simple values (e.g., strings or plain objects).
32+
* - Values with additional localization support for display in different locales.
33+
* - Wrapper types that encapsulate richer behaviors for combo box items.
34+
*
35+
* This interface is marked as experimental and may change in future releases.
36+
*
37+
* @since 6.0.0
38+
*/
39+
@API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
40+
public interface ComboBoxValue
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.jorphan.locale
19+
20+
import org.apiguardian.api.API
21+
import org.jetbrains.annotations.NonNls
22+
23+
@API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
24+
public class LocalizedString(
25+
override val resourceKey: @NonNls String,
26+
private val resourceLocalizer: ResourceLocalizer,
27+
) : ResourceKeyed, ComboBoxValue {
28+
override fun toString(): String =
29+
resourceLocalizer.localize(resourceKey)
30+
31+
override fun equals(other: Any?): Boolean =
32+
other is LocalizedString && other.resourceKey == resourceKey
33+
34+
override fun hashCode(): Int =
35+
resourceKey.hashCode()
36+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.jorphan.locale
19+
20+
import org.apiguardian.api.API
21+
22+
/**
23+
* Represents a localized value that combines an object implementing the [ResourceKeyed] interface
24+
* with a localization function. This allows the object's resource key to be localized dynamically
25+
* into a string representation.
26+
*
27+
* @param T The type of the value, which must implement the [ResourceKeyed] interface.
28+
* @property value The object implementing [ResourceKeyed], which holds a resource key for localization.
29+
* @property localizer A function that takes a resource key string and returns its localized string representation.
30+
*
31+
* The localized representation of this object is produced by applying the `localizer` function
32+
* to the resource key of the `value`.
33+
*
34+
* The class provides equality and hash code implementations based on the underlying `value`.
35+
*
36+
* @since 6.0.0
37+
*/
38+
@API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
39+
public class LocalizedValue<T : ResourceKeyed>(
40+
public val value: T,
41+
private val localizer: ResourceLocalizer,
42+
) : ResourceKeyed by value, ComboBoxValue {
43+
override fun toString(): String =
44+
localizer.localize(value.resourceKey)
45+
46+
override fun equals(other: Any?): Boolean =
47+
other is LocalizedValue<*> && other.value == value
48+
49+
override fun hashCode(): Int =
50+
value.hashCode()
51+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.jorphan.locale
19+
20+
import org.apiguardian.api.API
21+
22+
@JvmInline
23+
@API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
24+
public value class PlainValue(public val value: String): ComboBoxValue {
25+
override fun toString(): String = value
26+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.jorphan.locale
19+
20+
import org.apiguardian.api.API
21+
import org.jetbrains.annotations.NonNls
22+
23+
/**
24+
* Interface for enum types that provide their own localization resource key for GUI display.
25+
*
26+
* When an enum implements this interface, the [resourceKey] will be used for:
27+
* - Storing the value in JMeter properties (instead of the enum name)
28+
* - Looking up localized display text via `JMeterUtils.getResString(resourceKey)`
29+
* - Binding to GUI components
30+
*
31+
* This allows enums to have stable, localization-friendly identifiers that are independent
32+
* of the enum constant names.
33+
*
34+
* Example:
35+
* ```java
36+
* public enum ResponseMode implements ResourceKeyed {
37+
* STORE_COMPRESSED("response_mode_store"),
38+
* FETCH_DISCARD("response_mode_discard");
39+
*
40+
* private final String key;
41+
* ResponseMode(String key) { this.key = key; }
42+
*
43+
* @Override
44+
* public String getResourceKey() { return key; }
45+
* }
46+
* ```
47+
*
48+
* @since 6.0.0
49+
*/
50+
@API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
51+
public interface ResourceKeyed {
52+
/**
53+
* Returns the resource key used for localization and property storage.
54+
*
55+
* This key should:
56+
* - Be stable across JMeter versions (don't change it)
57+
* - Have a corresponding entry in messages.properties
58+
* - Use lowercase with underscores by convention
59+
*
60+
* @return the resource key for this enum value
61+
*/
62+
public val resourceKey: @NonNls String
63+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.jorphan.locale
19+
20+
import org.apiguardian.api.API
21+
import org.jetbrains.annotations.Nls
22+
import org.jetbrains.annotations.NonNls
23+
24+
/**
25+
* Functional interface for localizing resource strings.
26+
*
27+
* This interface provides a single method, [localize], which takes a non-localized input string
28+
* and returns its localized representation. Implementations of this interface define how the
29+
* localization is performed, such as looking up translations in a resource bundle or applying
30+
* other localization mechanisms.
31+
*
32+
* The input string is expected to be a resource identifier or key, and the output is a string
33+
* appropriate for display in a user interface or other localized context.
34+
*
35+
* @since 6.0.0
36+
*/
37+
@API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
38+
public fun interface ResourceLocalizer {
39+
public fun localize(input: @NonNls String): @Nls String
40+
}

src/jorphan/src/main/kotlin/org/apache/jorphan/util/EnumUtils.kt

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
@file:JvmName("EnumUtils")
1919
package org.apache.jorphan.util
2020

21+
import org.apache.jorphan.locale.ResourceKeyed
2122
import org.apiguardian.api.API
2223
import java.util.Collections.unmodifiableList
2324
import java.util.Collections.unmodifiableMap
@@ -39,34 +40,30 @@ private val VALUE_MAP = object : ClassValue<Map<String, Enum<*>>>() {
3940
require(type.isEnum) {
4041
"Class $type is not an enum"
4142
}
42-
@Suppress("UNCHECKED_CAST")
43-
type as Class<Enum<*>>
4443
return unmodifiableMap(
45-
VALUES.get(type).associateBy { it.stringValue }
44+
VALUES.get(type).associateBy {
45+
(it as ResourceKeyed).resourceKey
46+
}
4647
)
4748
}
4849
}
4950

5051
@Suppress("UNCHECKED_CAST")
51-
@API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
52-
public fun <T : Enum<*>> values(klass: Class<out T>): List<T> =
53-
VALUES.get(klass) as List<T>
52+
@get:API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
53+
public val <T : Enum<*>> Class<out T>.enumValues: List<T>
54+
get() = VALUES.get(this) as List<T>
5455

5556
@Suppress("UNCHECKED_CAST")
56-
@API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
57-
public fun <T : Enum<*>> valueMap(klass: Class<out T>): List<T> =
58-
VALUES.get(klass) as List<T>
57+
@get:API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
58+
public val <T> Class<out T>.enumValueMap: Map<String, T> where T : Enum<*>, T : ResourceKeyed
59+
get() = VALUE_MAP.get(this) as Map<String, T>
5960

6061
@Suppress("UNCHECKED_CAST")
6162
@API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
62-
public inline fun <reified T : Enum<*>> valueOf(value: String): T? =
63-
valueOf(T::class.java, value)
63+
public inline fun <reified T> valueOf(value: String): T? where T : Enum<*>, T : ResourceKeyed =
64+
T::class.java.valueOf(value)
6465

6566
@Suppress("UNCHECKED_CAST")
6667
@API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
67-
public fun <T : Enum<*>> valueOf(klass: Class<T>, value: String): T? =
68-
VALUE_MAP.get(klass)[value] as T?
69-
70-
@get:API(status = API.Status.EXPERIMENTAL, since = "6.0.0")
71-
public val Enum<*>.stringValue: String
72-
get() = toString()
68+
public fun <T> Class<T>.valueOf(value: String): T? where T : Enum<*>, T : ResourceKeyed =
69+
enumValueMap[value]

0 commit comments

Comments
 (0)