Reject write methods not starting with "set" in Property

Prior to this commit, Property.resolveName() located the "set" prefix
of a write method with String.indexOf(), which matches the token
anywhere in the method name. A write method that merely contains "set"
(for example "offsetX" or "upset") was silently accepted and resolved
to a meaningless property name derived from whatever follows the token,
while only names with no "set" token at all were rejected.

To address that, this commit matches the "set" prefix only at the start
of the method name via startsWith(), so that an
IllegalArgumentException is consistently thrown for any write method
candidate that is not a setter.

See gh-36911
Closes gh-37139

Signed-off-by: junhyeong9812 <pickjog@gmail.com>
This commit is contained in:
junhyeong9812
2026-08-19 18:26:46 +02:00
committed by Sam Brannen
parent f067d40f0a
commit e8e293a706
2 changed files with 40 additions and 6 deletions
@@ -153,12 +153,11 @@ public final class Property {
return StringUtils.uncapitalize(methodName.substring(index));
}
else if (this.writeMethod != null) {
int index = this.writeMethod.getName().indexOf("set");
if (index == -1) {
String methodName = this.writeMethod.getName();
if (!methodName.startsWith("set")) {
throw new IllegalArgumentException("Not a setter method");
}
index += 3;
return StringUtils.uncapitalize(this.writeMethod.getName().substring(index));
return StringUtils.uncapitalize(methodName.substring(3));
}
else {
throw new IllegalStateException("Property is neither readable nor writable");
@@ -21,6 +21,7 @@ import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link Property} name resolution.
@@ -47,8 +48,28 @@ class PropertyTests {
@Test
void resolveNameForSetter() throws Exception {
Method setter = TestBean.class.getMethod("setName", String.class);
assertThat(new Property(TestBean.class, null, setter).getName()).isEqualTo("name");
assertThat(writeProperty("setName").getName()).isEqualTo("name");
}
@Test // no "set" token at all: rejected before and after this change
void rejectNonSetterWriteMethod() {
assertThatIllegalArgumentException()
.isThrownBy(() -> writeProperty("updateName"))
.withMessage("Not a setter method");
}
@Test // "set" embedded mid-name: formerly accepted and resolved to "x"
void rejectWriteMethodEmbeddingSetInName() {
assertThatIllegalArgumentException()
.isThrownBy(() -> writeProperty("offsetX"))
.withMessage("Not a setter method");
}
@Test // "set" at the end of the name: formerly accepted and resolved to ""
void rejectWriteMethodEndingWithSetToken() {
assertThatIllegalArgumentException()
.isThrownBy(() -> writeProperty("upset"))
.withMessage("Not a setter method");
}
@Test // record component accessor whose name embeds the "get" prefix
@@ -127,6 +148,11 @@ class PropertyTests {
return new Property(objectType, readMethod, null);
}
private static Property writeProperty(String writeMethodName) throws Exception {
Method writeMethod = TestBean.class.getMethod(writeMethodName, String.class);
return new Property(TestBean.class, null, writeMethod);
}
@SuppressWarnings("unused")
static class TestBean {
@@ -145,6 +171,15 @@ class PropertyTests {
public void setName(String name) {
}
public void updateName(String name) {
}
public void offsetX(String value) {
}
public void upset(String value) {
}
}
record SampleRecord(String name, String budget, String issue) {