Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.RecordComponent;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -133,35 +134,48 @@ Annotation[] getAnnotations() {

private String resolveName() {
if (this.readMethod != null) {
int index = this.readMethod.getName().indexOf("get");
if (index != -1) {
index += 3;
String methodName = this.readMethod.getName();
int index;
if (isRecordAccessor(this.readMethod)) {
// Record-style plain accessor method, for example, name()
index = 0;
}
else if (methodName.startsWith("get")) {
index = 3;
}
else if (methodName.startsWith("is")) {
index = 2;
}
else {
index = this.readMethod.getName().indexOf("is");
if (index != -1) {
index += 2;
}
else {
// Record-style plain accessor method, for example, name()
index = 0;
}
index = 0;
}
return StringUtils.uncapitalize(this.readMethod.getName().substring(index));
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");
}
}

private static boolean isRecordAccessor(Method method) {
Class<?> declaringClass = method.getDeclaringClass();
if (!declaringClass.isRecord()) {
return false;
}
for (RecordComponent component : declaringClass.getRecordComponents()) {
if (component.getAccessor().equals(method)) {
return true;
}
}
return false;
}

private MethodParameter resolveMethodParameter() {
MethodParameter read = resolveReadMethodParameter();
MethodParameter write = resolveWriteMethodParameter();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.core.convert;

import java.lang.reflect.Method;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for {@link Property} name resolution.
*
* @author junhyeong9812
*/
class PropertyTests {

@Test
void resolveNameForStandardGetter() throws Exception {
assertThat(readProperty(TestBean.class, "getName").getName()).isEqualTo("name");
}

@Test
void resolveNameForBooleanGetter() throws Exception {
assertThat(readProperty(TestBean.class, "isEnabled").getName()).isEqualTo("enabled");
}

@Test
void resolveNameForSetter() throws Exception {
Method setter = TestBean.class.getMethod("setName", String.class);
assertThat(new Property(TestBean.class, null, setter).getName()).isEqualTo("name");
}

@Test // record component accessor whose name embeds the "get" prefix
void resolveNameForRecordAccessorEmbeddingGetPrefix() throws Exception {
assertThat(readProperty(SampleRecord.class, "budget").getName()).isEqualTo("budget");
}

@Test // record component accessor whose name starts with the "is" prefix
void resolveNameForRecordAccessorStartingWithIsPrefix() throws Exception {
assertThat(readProperty(SampleRecord.class, "issue").getName()).isEqualTo("issue");
}

@Test // plain record component accessor with no prefix collision (regression guard)
void resolveNameForPlainRecordAccessor() throws Exception {
assertThat(readProperty(SampleRecord.class, "name").getName()).isEqualTo("name");
}

@Test // a JavaBeans-style getter declared on a record must still be stripped
void resolveNameForGetterDeclaredOnRecord() throws Exception {
assertThat(readProperty(SampleRecord.class, "getWidget").getName()).isEqualTo("widget");
}

@Test // component literally named "get": proves record detection must precede startsWith
void resolveNameForRecordAccessorNamedGet() throws Exception {
assertThat(readProperty(EdgeRecord.class, "get").getName()).isEqualTo("get");
}

@Test // component literally named "is": proves record detection must precede startsWith
void resolveNameForRecordAccessorNamedIs() throws Exception {
assertThat(readProperty(EdgeRecord.class, "is").getName()).isEqualTo("is");
}


private static Property readProperty(Class<?> objectType, String readMethodName) throws Exception {
Method readMethod = objectType.getMethod(readMethodName);
return new Property(objectType, readMethod, null);
}


@SuppressWarnings("unused")
static class TestBean {

public String getName() {
return null;
}

public boolean isEnabled() {
return false;
}

public void setName(String name) {
}
}

record SampleRecord(String name, String budget, String issue) {

public String getWidget() {
return null;
}
}

record EdgeRecord(String get, String is) {
}

}