Skip to content

Commit

Permalink
Fix regression with chained setters
Browse files Browse the repository at this point in the history
Resolves #437
  • Loading branch information
seregamorph authored and fmbenhassine committed Nov 3, 2020
1 parent dcc90eb commit e86984d
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 47 deletions.
4 changes: 0 additions & 4 deletions easy-random-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,11 @@
*/
package org.jeasy.random.util;

import org.apache.commons.beanutils.FluentPropertyBeanIntrospector;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.jeasy.random.annotation.RandomizerArgument;
import org.jeasy.random.ObjectCreationException;
import org.jeasy.random.api.Randomizer;
import org.objenesis.ObjenesisStd;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.math.BigDecimal;
Expand Down Expand Up @@ -60,11 +56,6 @@
*/
public final class ReflectionUtils {

private static final PropertyUtilsBean PROPERTY_UTILS_BEAN = new PropertyUtilsBean();
static {
PROPERTY_UTILS_BEAN.addBeanIntrospector(new FluentPropertyBeanIntrospector());
}

private ReflectionUtils() {
}

Expand Down Expand Up @@ -140,21 +131,16 @@ public static List<Field> getInheritedFields(Class<?> type) {
* @throws IllegalAccessException if the property cannot be set
*/
public static void setProperty(final Object object, final Field field, final Object value) throws IllegalAccessException, InvocationTargetException {
try { // to call regular setter
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), object.getClass());
Method setter = propertyDescriptor.getWriteMethod();
if (setter != null) {
setter.invoke(object, value);
try {
Optional<Method> setter = getWriteMethod(field);
if (setter.isPresent()) {
setter.get().invoke(object, value);
} else {
setFieldValue(object, field, value);
}
} catch (IntrospectionException | IllegalAccessException e) {
try { // to call fluent setter
PROPERTY_UTILS_BEAN.setProperty(object, field.getName(), value);
} catch (NoSuchMethodException noSuchMethodException) {
// otherwise, set field using reflection
setFieldValue(object, field, value);
}
} catch (IllegalAccessException e) {
// otherwise, set field using reflection
setFieldValue(object, field, value);
}
}

Expand Down Expand Up @@ -551,6 +537,16 @@ private static void rejectUnsupportedTypes(Class<?> type) {
}
}

/**
* Get the write method for given field.
*
* @param field field to get the write method for
* @return Optional of write method or empty if field has no write method
*/
private static Optional<Method> getWriteMethod(Field field) {
return getPublicMethod("set" + capitalize(field.getName()), field.getDeclaringClass(), field.getType());
}

/**
* Get the read method for given field.
* @param field field to get the read method for.
Expand All @@ -559,7 +555,7 @@ private static void rejectUnsupportedTypes(Class<?> type) {
public static Optional<Method> getReadMethod(Field field) {
String fieldName = field.getName();
Class<?> fieldClass = field.getDeclaringClass();
String capitalizedFieldName = fieldName.substring(0, 1).toUpperCase(ENGLISH) + fieldName.substring(1);
String capitalizedFieldName = capitalize(fieldName);
// try to find getProperty
Optional<Method> getter = getPublicMethod("get" + capitalizedFieldName, fieldClass);
if (getter.isPresent()) {
Expand All @@ -569,9 +565,13 @@ public static Optional<Method> getReadMethod(Field field) {
return getPublicMethod("is" + capitalizedFieldName, fieldClass);
}

private static Optional<Method> getPublicMethod(String name, Class<?> target) {
private static String capitalize(String propertyName) {
return propertyName.substring(0, 1).toUpperCase(ENGLISH) + propertyName.substring(1);
}

private static Optional<Method> getPublicMethod(String name, Class<?> target, Class<?>... parameterTypes) {
try {
return Optional.of(target.getMethod(name));
return Optional.of(target.getMethod(name, parameterTypes));
} catch (NoSuchMethodException | SecurityException e) {
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* The MIT License
*
* Copyright (c) 2020, Mahmoud Ben Hassine ([email protected])
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jeasy.random;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

public class EasyRandomChainedSettersTest {

@Test
void chainSettersShouldBeRandomized() {
EasyRandom easyRandom = new EasyRandom(new EasyRandomParameters());

SubClassA value = easyRandom.nextObject(SubClassA.class);

assertNotNull(value.getField());
assertTrue(value.getF() != 0);
}

public static class BaseClass {

// setter overriden
private String field;
// single-char name
private int f;

public BaseClass setField(String field) {
this.field = field;
return this;
}

public String getField() {
return field;
}

public int getF() {
return f;
}

public BaseClass setF(int f) {
this.f = f;
return this;
}
}

public static class SubClassA extends BaseClass {

private SubClassB subClassB;
}

public static class SubClassB extends BaseClass {

@Override
public SubClassB setField(String field) {
super.setField(field);
return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* The MIT License
*
* Copyright (c) 2020, Mahmoud Ben Hassine ([email protected])
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jeasy.random;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

public class EasyRandomOverridenFieldTest {

@Test
void chainSettersShouldBeRandomized() {
EasyRandom easyRandom = new EasyRandom(new EasyRandomParameters());

SubClass value = easyRandom.nextObject(SubClass.class);

assertFalse(((BaseClass) value).field.isEmpty());
assertTrue(value.field != 0);
}

public static class BaseClass {

// field overriden (note: not setter)
private String field;

public BaseClass setField(String field) {
this.field = field;
return this;
}
}

public static class SubClass extends BaseClass {

// note: field overriden
private int field;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ void generatedBeansShouldBeCorrectlyPopulated() {
@Test
void generatedBeanWithFluentSetterShouldBeCorrectlyPopulated() {
// when
FluentSetterBean fluentSetterBean = easyRandom.nextObject(FluentSetterBean.class);
ChainedSetterBean chainedSetterBean = easyRandom.nextObject(ChainedSetterBean.class);

// then
assertThat(fluentSetterBean.getName()).isNotEmpty();
assertThat(chainedSetterBean.getName()).isNotEmpty();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,27 @@
*/
package org.jeasy.random.beans;

public class FluentSetterBean {
public class ChainedSetterBean {

public String name;
private String name;
private int index;

public FluentSetterBean setName(String name) {
public ChainedSetterBean setName(String name) {
this.name = name;
return this;
}

public ChainedSetterBean setIndex(int index) {
this.index = index;
return this;
}

public String getName() {
return name;
}

public int getIndex() {
return index;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@

import org.jeasy.random.beans.*;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
Expand All @@ -43,6 +41,8 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

class ReflectionUtilsTest {

Expand Down Expand Up @@ -226,17 +226,31 @@ void getEmptyImplementationForMapInterface() {
}

@Test
void setPropertyFluentBean() throws NoSuchFieldException, InvocationTargetException, IllegalAccessException {
void setPropertyFluentBean() throws Exception {
// given
FluentSetterBean fluentSetterBean = Mockito.spy(FluentSetterBean.class);
Field nameField = FluentSetterBean.class.getField("name");
ChainedSetterBean chainedSetterBean = spy(ChainedSetterBean.class);
Field nameField = ChainedSetterBean.class.getDeclaredField("name");

// when
ReflectionUtils.setProperty(fluentSetterBean,nameField,"myName");
ReflectionUtils.setProperty(chainedSetterBean, nameField, "myName");

// then
Mockito.verify(fluentSetterBean).setName("myName");
assertThat(fluentSetterBean.getName()).isEqualTo("myName");
verify(chainedSetterBean).setName("myName");
assertThat(chainedSetterBean.getName()).isEqualTo("myName");
}

@Test
void setPropertyFluentBeanPrimitiveType() throws Exception {
// given
ChainedSetterBean chainedSetterBean = spy(ChainedSetterBean.class);
Field indexField = ChainedSetterBean.class.getDeclaredField("index");

// when
ReflectionUtils.setProperty(chainedSetterBean, indexField, 100);

// then
verify(chainedSetterBean).setIndex(100);
assertThat(chainedSetterBean.getIndex()).isEqualTo(100);
}

@SuppressWarnings("unused")
Expand Down
6 changes: 0 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
<java.version>1.8</java.version>
<junit.version>5.6.0</junit.version>
<faker.version>1.0.2</faker.version>
<commons-beanutils-version>1.9.4</commons-beanutils-version>
<assertj.version>3.15.0</assertj.version>
<validation-api.version>2.0.1.Final</validation-api.version>
<objenesis.version>3.1</objenesis.version>
Expand Down Expand Up @@ -129,11 +128,6 @@
<artifactId>javafaker</artifactId>
<version>${faker.version}</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${commons-beanutils-version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down

0 comments on commit e86984d

Please sign in to comment.