Skip to content

Commit 5fefa6e

Browse files
committed
feat: fallback to English locale when loading test plan with string value in enum property
Previously only the current locale was considered, and now we fallback to English. It helps for cases like loading sample test plans (saved years ago) when JMeter runs with non-English locale.
1 parent 920fa60 commit 5fefa6e

8 files changed

Lines changed: 267 additions & 90 deletions

File tree

src/components/src/main/java/org/apache/jmeter/config/CSVDataSet.java

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@
1717

1818
package org.apache.jmeter.config;
1919

20-
import java.beans.BeanInfo;
21-
import java.beans.IntrospectionException;
22-
import java.beans.Introspector;
2320
import java.io.IOException;
24-
import java.util.ResourceBundle;
2521

2622
import org.apache.jmeter.engine.event.LoopIterationEvent;
2723
import org.apache.jmeter.engine.event.LoopIterationListener;
@@ -121,38 +117,15 @@ private Object readResolve(){
121117
*/
122118
@Override
123119
public void setProperty(JMeterProperty property) {
124-
if (!(property instanceof StringProperty stringProperty)) {
125-
super.setProperty(property);
126-
return;
127-
}
128-
129-
final String propName = property.getName();
130-
if (!"shareMode".equals(propName)) {
131-
super.setProperty(property);
132-
return;
133-
}
134-
135-
final String propValue = property.getStringValue();
136-
if (propValue.contains(" ")) { // variables are unlikely to contain spaces, so most likely a translation
137-
try {
138-
final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass());
139-
final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE);
140-
for (String resKey : CSVDataSetBeanInfo.getShareTags()) {
141-
if (propValue.equals(rb.getString(resKey))) {
142-
if (log.isDebugEnabled()) {
143-
log.debug("Converted {}={} to {} using Locale: {}", propName, propValue, resKey, rb.getLocale());
144-
}
145-
stringProperty.setValue(resKey); // reset the value
146-
super.setProperty(property);
147-
return;
148-
}
149-
}
150-
// This could perhaps be a variable name
151-
log.warn("Could not translate {}={} using Locale: {}", propName, propValue, rb.getLocale());
152-
} catch (IntrospectionException e) {
153-
log.error("Could not find BeanInfo; cannot translate shareMode entries", e);
120+
String propName = property.getName();
121+
if ("shareMode".equals(propName)) {
122+
String enumLabel = GenericTestBeanCustomizer.normalizeEnumStringValue(getClass(), CSVDataSetBeanInfo.ShareMode.class, property);
123+
if (enumLabel != null && (!(property instanceof StringProperty) || !enumLabel.equals(property.getStringValue()))) {
124+
super.setProperty(propName, enumLabel);
125+
return;
154126
}
155127
}
128+
156129
super.setProperty(property);
157130
}
158131

src/components/src/main/java/org/apache/jmeter/config/CSVDataSetBeanInfo.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,34 @@ public class CSVDataSetBeanInfo extends BeanInfoSupport {
4040
private static final String SHAREMODE = "shareMode"; //$NON-NLS-1$
4141

4242
// Access needed from CSVDataSet
43+
enum ShareMode {
44+
ALL("shareMode.all"),
45+
GROUP("shareMode.group"),
46+
THREAD("shareMode.thread");
47+
48+
private final String value;
49+
50+
ShareMode(String value) {
51+
this.value = value;
52+
}
53+
54+
@Override
55+
public String toString() {
56+
return value;
57+
}
58+
}
4359
private static final String[] SHARE_TAGS = new String[3];
4460
static final int SHARE_ALL = 0;
4561
static final int SHARE_GROUP = 1;
4662
static final int SHARE_THREAD = 2;
4763

4864
// Store the resource keys
4965
static {
50-
SHARE_TAGS[SHARE_ALL] = "shareMode.all"; //$NON-NLS-1$
51-
SHARE_TAGS[SHARE_GROUP] = "shareMode.group"; //$NON-NLS-1$
52-
SHARE_TAGS[SHARE_THREAD] = "shareMode.thread"; //$NON-NLS-1$
66+
for (ShareMode value : ShareMode.values()) {
67+
@SuppressWarnings("EnumOrdinal")
68+
int index = value.ordinal();
69+
SHARE_TAGS[index] = value.toString();
70+
}
5371
}
5472

5573
public CSVDataSetBeanInfo() {

src/components/src/main/java/org/apache/jmeter/timers/ConstantThroughputTimer.java

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@
1717

1818
package org.apache.jmeter.timers;
1919

20-
import java.beans.BeanInfo;
21-
import java.beans.IntrospectionException;
22-
import java.beans.Introspector;
23-
import java.util.ResourceBundle;
2420
import java.util.concurrent.ConcurrentHashMap;
2521
import java.util.concurrent.ConcurrentMap;
2622
import java.util.concurrent.atomic.AtomicLong;
@@ -38,8 +34,6 @@
3834
import org.apache.jmeter.threads.JMeterContextService;
3935
import org.apache.jmeter.util.JMeterUtils;
4036
import org.apache.jorphan.collections.IdentityKey;
41-
import org.slf4j.Logger;
42-
import org.slf4j.LoggerFactory;
4337

4438
/**
4539
* This class implements a constant throughput timer. A Constant Throughput
@@ -59,7 +53,6 @@ private static class ThroughputInfo{
5953
final Object MUTEX = new Object();
6054
long lastScheduledTime = 0;
6155
}
62-
private static final Logger log = LoggerFactory.getLogger(ConstantThroughputTimer.class);
6356
private static final AtomicLong PREV_TEST_STARTED = new AtomicLong(0L);
6457

6558
private static final double MILLISEC_PER_MIN = 60000.0;
@@ -271,27 +264,12 @@ public String toString() {
271264
@Override
272265
@SuppressWarnings("EnumOrdinal")
273266
public void setProperty(JMeterProperty property) {
274-
if (property instanceof StringProperty) {
275-
final String pn = property.getName();
276-
if (pn.equals("calcMode")) {
277-
final Object objectValue = property.getObjectValue();
278-
try {
279-
final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass());
280-
final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE);
281-
for(Enum<Mode> e : Mode.CACHED_VALUES) {
282-
final String propName = e.toString();
283-
if (objectValue.equals(rb.getObject(propName))) {
284-
final int tmpMode = e.ordinal();
285-
log.debug("Converted {}={} to mode={} using Locale: {}", pn, objectValue, tmpMode,
286-
rb.getLocale());
287-
super.setProperty(pn, tmpMode);
288-
return;
289-
}
290-
}
291-
log.warn("Could not convert {}={} using Locale: {}", pn, objectValue, rb.getLocale());
292-
} catch (IntrospectionException e) {
293-
log.error("Could not find BeanInfo", e);
294-
}
267+
String propertyName = property.getName();
268+
if (propertyName.equals("calcMode")) {
269+
String enumLabel = GenericTestBeanCustomizer.normalizeEnumStringValue(getClass(), Mode.class, property);
270+
if (enumLabel != null && (!(property instanceof StringProperty) || !enumLabel.equals(property.getStringValue()))) {
271+
super.setProperty(propertyName, enumLabel);
272+
return;
295273
}
296274
}
297275
super.setProperty(property);

src/core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dependencies {
4343
// We use StaXDriver, so we exclude xmlpull, see https://x-stream.github.io/download.html#optional-deps
4444
exclude("io.github.x-stream", "mxparser")
4545
}
46+
api("org.jspecify:jspecify")
4647
api("org.apache.logging.log4j:log4j-1.2-api")
4748
api("org.apache.logging.log4j:log4j-api")
4849
api("org.apache.logging.log4j:log4j-core") {

src/core/src/main/java/org/apache/jmeter/testbeans/gui/ComboStringEditor.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,15 @@ public String getAsText() {
191191
*/
192192
@Override
193193
public void setValue(Object value) {
194-
setAsText((String) value);
194+
if (value == null) {
195+
setAsText(null);
196+
return;
197+
}
198+
if (value instanceof String literal) {
199+
setAsText(literal);
200+
return;
201+
}
202+
throw new IllegalArgumentException("Expected String but got " + value.getClass() + ", value=" + value);
195203
}
196204

197205
/**

src/core/src/main/java/org/apache/jmeter/testbeans/gui/EnumEditor.java

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,18 @@
2020
import java.awt.Component;
2121
import java.beans.PropertyDescriptor;
2222
import java.beans.PropertyEditorSupport;
23+
import java.util.List;
2324
import java.util.ResourceBundle;
2425

26+
import javax.swing.ComboBoxModel;
2527
import javax.swing.DefaultComboBoxModel;
28+
import javax.swing.DefaultListCellRenderer;
2629
import javax.swing.JComboBox;
30+
import javax.swing.JLabel;
31+
import javax.swing.JList;
2732

2833
import org.apache.jmeter.gui.ClearGui;
34+
import org.apache.jorphan.util.EnumUtils;
2935

3036
/**
3137
* This class implements a property editor for String properties based on an enum
@@ -37,26 +43,38 @@
3743
*/
3844
class EnumEditor extends PropertyEditorSupport implements ClearGui {
3945

40-
private final JComboBox<String> combo;
46+
private final JComboBox<Enum<?>> combo;
4147

42-
private final DefaultComboBoxModel<String> model;
43-
44-
private final int defaultIndex;
48+
private final Enum<?> defaultValue;
4549

4650
public EnumEditor(final PropertyDescriptor descriptor, final Class<? extends Enum<?>> enumClazz, final ResourceBundle rb) {
47-
model = new DefaultComboBoxModel<>();
51+
DefaultComboBoxModel<Enum<?>> model = new DefaultComboBoxModel<>();
4852
combo = new JComboBox<>(model);
4953
combo.setEditable(false);
50-
for(Enum<?> e : enumClazz.getEnumConstants()) {
51-
model.addElement((String) rb.getObject(e.toString()));
54+
combo.setRenderer(
55+
new DefaultListCellRenderer() {
56+
@Override
57+
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
58+
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
59+
Enum<?> enumValue = (Enum<?>) value;
60+
label.setText(rb.getString(EnumUtils.getStringValue(enumValue)));
61+
return label;
62+
}
63+
}
64+
);
65+
List<? extends Enum<?>> values = EnumUtils.values(enumClazz);
66+
for(Enum<?> e : values) {
67+
model.addElement(e);
5268
}
5369
Object def = descriptor.getValue(GenericTestBeanCustomizer.DEFAULT);
54-
if (def instanceof Integer integer) {
55-
defaultIndex = integer;
70+
if (def instanceof Enum<?> enumValue) {
71+
defaultValue = enumValue;
72+
} else if (def instanceof Integer index) {
73+
defaultValue = values.get(index);
5674
} else {
57-
defaultIndex = 0;
75+
defaultValue = values.get(0);
5876
}
59-
combo.setSelectedIndex(defaultIndex);
77+
combo.setSelectedItem(defaultValue);
6078
}
6179

6280
@Override
@@ -71,35 +89,35 @@ public Component getCustomEditor() {
7189

7290
@Override
7391
public Object getValue() {
74-
return combo.getSelectedIndex();
92+
return combo.getSelectedItem();
7593
}
7694

7795
@Override
78-
public String getAsText() {
79-
Object value = combo.getSelectedItem();
80-
return (String) value;
81-
}
82-
83-
@Override
84-
@SuppressWarnings("EnumOrdinal")
8596
public void setValue(Object value) {
8697
if (value instanceof Enum<?> anEnum){
87-
combo.setSelectedIndex(anEnum.ordinal());
98+
combo.setSelectedItem(anEnum);
8899
} else if (value instanceof Integer integer) {
89100
combo.setSelectedIndex(integer);
90-
} else {
91-
combo.setSelectedItem(value);
101+
} else if (value instanceof String string) {
102+
ComboBoxModel<Enum<?>> model = combo.getModel();
103+
for (int i = 0; i < model.getSize(); i++) {
104+
Enum<?> element = model.getElementAt(i);
105+
if (EnumUtils.getStringValue(element).equals(string)) {
106+
combo.setSelectedItem(element);
107+
return;
108+
}
109+
}
92110
}
93111
}
94112

95113
@Override
96114
public void setAsText(String value) {
97-
combo.setSelectedItem(value);
115+
throw new UnsupportedOperationException("Not supported yet. Use enum value rather than text, got " + value);
98116
}
99117

100118
@Override
101119
public void clearGui() {
102-
combo.setSelectedIndex(defaultIndex);
120+
combo.setSelectedItem(defaultValue);
103121
}
104122

105123
}

0 commit comments

Comments
 (0)