diff --git a/src/main/java/org/springframework/data/util/KotlinBeanInfoFactory.java b/src/main/java/org/springframework/data/util/KotlinBeanInfoFactory.java index d7e0a6d29..ae2bc6b48 100644 --- a/src/main/java/org/springframework/data/util/KotlinBeanInfoFactory.java +++ b/src/main/java/org/springframework/data/util/KotlinBeanInfoFactory.java @@ -40,6 +40,7 @@ import org.springframework.core.KotlinDetector; import org.springframework.core.Ordered; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -112,8 +113,13 @@ public class KotlinBeanInfoFactory implements BeanInfoFactory, Ordered { for (PropertyDescriptor descriptor : javaPropertyDescriptors) { - descriptor = new PropertyDescriptor(descriptor.getName(), specialize(beanClass, descriptor.getReadMethod()), - specialize(beanClass, descriptor.getWriteMethod())); + Method getter = specialize(beanClass, descriptor.getReadMethod()); + Method setter = specialize(beanClass, descriptor.getWriteMethod()); + + if (!ObjectUtils.nullSafeEquals(descriptor.getReadMethod(), getter) + || !ObjectUtils.nullSafeEquals(descriptor.getWriteMethod(), setter)) { + descriptor = new BasicPropertyDescriptor(descriptor.getName(), getter, setter); + } descriptors.put(descriptor.getName(), descriptor); } } @@ -148,4 +154,44 @@ public class KotlinBeanInfoFactory implements BeanInfoFactory, Ordered { return LOWEST_PRECEDENCE - 10; // leave some space for customizations. } + /** + * PropertyDescriptor for {@link KotlinBeanInfoFactory}, not performing any early type determination for + * {@link #setReadMethod}/{@link #setWriteMethod}. + * + * @since 3.3.5 + */ + private static class BasicPropertyDescriptor extends PropertyDescriptor { + + private @Nullable Method readMethod; + + private @Nullable Method writeMethod; + + public BasicPropertyDescriptor(String propertyName, @Nullable Method readMethod, @Nullable Method writeMethod) + throws IntrospectionException { + + super(propertyName, readMethod, writeMethod); + } + + @Override + public void setReadMethod(@Nullable Method readMethod) { + this.readMethod = readMethod; + } + + @Override + @Nullable + public Method getReadMethod() { + return this.readMethod; + } + + @Override + public void setWriteMethod(@Nullable Method writeMethod) { + this.writeMethod = writeMethod; + } + + @Override + @Nullable + public Method getWriteMethod() { + return this.writeMethod; + } + } } diff --git a/src/test/java/org/springframework/data/util/AbstractAuditable.java b/src/test/java/org/springframework/data/util/AbstractAuditable.java new file mode 100644 index 000000000..d5390b7ea --- /dev/null +++ b/src/test/java/org/springframework/data/util/AbstractAuditable.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024 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.data.util; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.Optional; + +import org.springframework.lang.Nullable; + +public abstract class AbstractAuditable { + + private @Nullable Date createdDate; + + public Optional getCreatedDate() { + return null == createdDate ? Optional.empty() : Optional.of(LocalDateTime.now()); + } + + public void setCreatedDate(LocalDateTime createdDate) { + this.createdDate = Date.from(createdDate.atZone(ZoneId.systemDefault()).toInstant()); + } + +} diff --git a/src/test/kotlin/org/springframework/data/util/KotlinBeanInfoFactoryUnitTests.kt b/src/test/kotlin/org/springframework/data/util/KotlinBeanInfoFactoryUnitTests.kt index f02c98a87..16e714dd3 100644 --- a/src/test/kotlin/org/springframework/data/util/KotlinBeanInfoFactoryUnitTests.kt +++ b/src/test/kotlin/org/springframework/data/util/KotlinBeanInfoFactoryUnitTests.kt @@ -130,6 +130,13 @@ class KotlinBeanInfoFactoryUnitTests { ) } + @Test // GH-3167 + internal fun supportsPropertiesWithDifferentAccessorTypes() { + + val pds = BeanUtils.getPropertyDescriptors(User::class.java) + assertThat(pds).isNotEmpty + } + data class SimpleDataClass(val id: String, var name: String) @JvmInline @@ -189,4 +196,8 @@ class KotlinBeanInfoFactoryUnitTests { return super.getName() } } + + class User : AbstractAuditable() { + var name: String? = null + } }