Skip to content

Commit a013039

Browse files
committed
Added convenience methods for invoking bean setters / getters in an inheritance chain including value conversion
1 parent 3921db4 commit a013039

File tree

3 files changed

+143
-10
lines changed

3 files changed

+143
-10
lines changed

src/main/java/org/bbottema/javareflection/BeanUtils.java

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,22 @@
44
import org.bbottema.javareflection.util.commonslang25.StringUtils;
55
import org.bbottema.javareflection.model.FieldWrapper;
66
import org.bbottema.javareflection.model.InvokableObject;
7+
import org.bbottema.javareflection.valueconverter.IncompatibleTypeException;
8+
import org.bbottema.javareflection.valueconverter.ValueConversionHelper;
79
import org.jetbrains.annotations.NotNull;
810
import org.jetbrains.annotations.Nullable;
911

1012
import java.lang.reflect.Field;
1113
import java.lang.reflect.Method;
1214
import java.lang.reflect.Modifier;
13-
import java.util.Arrays;
14-
import java.util.EnumSet;
15-
import java.util.HashMap;
16-
import java.util.LinkedList;
17-
import java.util.List;
18-
import java.util.Map;
19-
import java.util.Set;
15+
import java.util.*;
2016
import java.util.regex.Pattern;
2117

18+
import static java.util.EnumSet.allOf;
19+
import static java.util.EnumSet.of;
2220
import static java.util.regex.Pattern.compile;
21+
import static org.bbottema.javareflection.BeanUtils.BeanRestriction.YES_SETTER;
22+
import static org.bbottema.javareflection.BeanUtils.BeanRestriction.YES_GETTER;
2323

2424
/**
2525
* A {@link Field} shorthand utility class used to collect fields from classes meeting Java Bean restrictions/requirements.
@@ -177,9 +177,9 @@ public static boolean methodIsBeanlike(Method method) {
177177
*/
178178
@SuppressWarnings("WeakerAccess")
179179
@NotNull
180-
public static Map<Class<?>, List<FieldWrapper>> collectFields(final Class<?> _class, final Class<?> boundaryMarker,
180+
public static LinkedHashMap<Class<?>, List<FieldWrapper>> collectFields(final Class<?> _class, final Class<?> boundaryMarker,
181181
final EnumSet<Visibility> visibility, final EnumSet<BeanRestriction> beanRestrictions) {
182-
final Map<Class<?>, List<FieldWrapper>> fields = new HashMap<>();
182+
final LinkedHashMap<Class<?>, List<FieldWrapper>> fields = new LinkedHashMap<>();
183183
final Field[] allFields = _class.getDeclaredFields();
184184
final List<FieldWrapper> filteredFields = new LinkedList<>();
185185
for (final Field field : allFields) {
@@ -261,4 +261,49 @@ static FieldWrapper resolveBeanProperty(final Field field, final EnumSet<BeanRes
261261
return null;
262262
}
263263
}
264+
265+
/**
266+
* Calls the setters for the first field that matches given fieldName. Attempts to convert the value in case the type is incorrect.
267+
*
268+
* @return The actual value used in the bean setter.
269+
*/
270+
@SuppressWarnings("ConstantConditions")
271+
static public Object invokeBeanSetter(Object o, String fieldName, Object value) {
272+
for (List<FieldWrapper> fieldWrappers : collectFields(o.getClass(), Object.class, allOf(Visibility.class), of(YES_SETTER)).values()) {
273+
for (FieldWrapper fieldWrapper : fieldWrappers) {
274+
if (fieldWrapper.getField().getName().equals(fieldName) ) {
275+
Object assignedValue = value;
276+
try {
277+
MethodUtils.invokeMethodSimple(fieldWrapper.getSetter(), o, value);
278+
} catch (final IllegalArgumentException ie) {
279+
try {
280+
assignedValue = ValueConversionHelper.convert(value, fieldWrapper.getField().getType());
281+
} catch (IncompatibleTypeException e) {
282+
throw new RuntimeException(new NoSuchMethodException(e.getMessage()));
283+
}
284+
MethodUtils.invokeMethodSimple(fieldWrapper.getSetter(), o, assignedValue);
285+
}
286+
return assignedValue;
287+
}
288+
}
289+
}
290+
throw new RuntimeException(new NoSuchMethodException("Bean setter for " + fieldName));
291+
}
292+
293+
/**
294+
* Calls the setters for the first field that matches given fieldName. Attempts to convert the value in case the type is incorrect.
295+
*
296+
* @return The actual value used in the bean setter.
297+
*/
298+
@SuppressWarnings("ConstantConditions")
299+
static public Object invokeBeanGetter(Object o, String fieldName) {
300+
for (List<FieldWrapper> fieldWrappers : collectFields(o.getClass(), Object.class, allOf(Visibility.class), of(YES_GETTER)).values()) {
301+
for (FieldWrapper fieldWrapper : fieldWrappers) {
302+
if (fieldWrapper.getField().getName().equals(fieldName) ) {
303+
return MethodUtils.invokeMethodSimple(fieldWrapper.getGetter(), o);
304+
}
305+
}
306+
}
307+
throw new RuntimeException(new NoSuchMethodException("Bean getter for " + fieldName));
308+
}
264309
}

src/main/java/org/bbottema/javareflection/MethodUtils.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,21 @@
6969
public final class MethodUtils {
7070

7171
private static final Logger LOGGER = getLogger(MethodUtils.class);
72-
72+
73+
@Nullable
74+
@SuppressWarnings({"unchecked"})
75+
public static <T> T invokeMethodSimple(final Method method, final Object subject, final Object... args) {
76+
try {
77+
return (T) method.invoke(subject, args);
78+
} catch (SecurityException e) {
79+
throw new RuntimeException("unable to invoke method; security problem", e);
80+
} catch (IllegalAccessException e) {
81+
throw new RuntimeException("unable to access method", e);
82+
} catch (InvocationTargetException e) {
83+
throw new RuntimeException("unable to invoke method", e);
84+
}
85+
}
86+
7387
/**
7488
* Locates a method on an Object using serveral searchmodes for optimization. First of all a {@link Method} cache is being maintained to quickly
7589
* fetch heavily used methods. If not cached before and if a simple search (autoboxing and supertype casts) fails a more complex search is done

src/test/java/org/bbottema/javareflection/BeanUtilsTest.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import java.lang.reflect.Field;
99
import java.lang.reflect.Method;
10+
import java.math.BigDecimal;
1011
import java.util.EnumSet;
1112
import java.util.List;
1213
import java.util.Map;
@@ -321,6 +322,79 @@ public void testResolveBeanPropertyExceptions()
321322
// ok
322323
}
323324
}
325+
326+
@Test
327+
public void testInvokeBeanSetter_SimpleSuccess() {
328+
BeanFields subject = new BeanFields();
329+
330+
assertThat(BeanUtils.invokeBeanSetter(subject, "withSetter", 123)).isEqualTo(123);
331+
assertThat(BeanUtils.invokeBeanSetter(subject, "withGetterAndSetter", true)).isEqualTo(true);
332+
assertThat(BeanUtils.invokeBeanSetter(subject, "primitiveBoolean", true)).isEqualTo(true);
333+
334+
assertThat(subject.withSetter).isEqualTo(123);
335+
assertThat(subject.getWithGetterAndSetter()).isEqualTo(true);
336+
assertThat(subject.isPrimitiveBoolean()).isTrue();
337+
}
338+
339+
@Test
340+
public void testInvokeBeanSetter_WithConversionSuccess() {
341+
BeanFields subject = new BeanFields();
342+
343+
assertThat(BeanUtils.invokeBeanSetter(subject, "primitiveBoolean", "true")).isEqualTo(true);
344+
assertThat(subject.isPrimitiveBoolean()).isTrue();
345+
assertThat(BeanUtils.invokeBeanSetter(subject, "primitiveBoolean", "false")).isEqualTo(false);
346+
assertThat(subject.isPrimitiveBoolean()).isFalse();
347+
}
348+
349+
@Test
350+
public void testInvokeBeanSetter_NoSetterForField() {
351+
BeanFields subject = new BeanFields();
352+
353+
try {
354+
BeanUtils.invokeBeanSetter(subject, "withGetter", 123);
355+
fail("expected exception");
356+
} catch (RuntimeException e) {
357+
assertThat(e.getCause()).isInstanceOf(NoSuchMethodException.class);
358+
assertThat(e.getCause().getMessage()).isEqualTo("Bean setter for withGetter");
359+
}
360+
}
361+
362+
@Test
363+
public void testInvokeBeanSetter_NoSetterForFieldWithCorrectType() {
364+
BeanFields subject = new BeanFields();
365+
366+
try {
367+
BeanUtils.invokeBeanSetter(subject, "primitiveBoolean", new Thread());
368+
fail("expected exception");
369+
} catch (RuntimeException e) {
370+
assertThat(e.getCause()).isInstanceOf(NoSuchMethodException.class);
371+
assertThat(e.getCause().getMessage()).contains("error: unable to convert value");
372+
}
373+
}
374+
375+
@Test
376+
public void testInvokeBeanGetter_SimpleSuccess() {
377+
BeanFields subject = new BeanFields();
378+
subject.setWithGetterAndSetter(true);
379+
subject.setPrimitiveBoolean(true);
380+
381+
assertThat(BeanUtils.invokeBeanGetter(subject, "withGetterAndSetter")).isEqualTo(true);
382+
assertThat(BeanUtils.invokeBeanGetter(subject, "primitiveBoolean")).isEqualTo(true);
383+
}
384+
385+
@Test
386+
public void testInvokeBeanGetter_NoGetterForField() {
387+
BeanFields subject = new BeanFields();
388+
subject.setWithSetter(true);
389+
390+
try {
391+
BeanUtils.invokeBeanGetter(subject, "withSetter");
392+
fail("expected exception");
393+
} catch (RuntimeException e) {
394+
assertThat(e.getCause()).isInstanceOf(NoSuchMethodException.class);
395+
assertThat(e.getCause().getMessage()).contains("Bean getter for withSetter");
396+
}
397+
}
324398

325399
@SuppressWarnings("unused")
326400
private static class BeanFields {

0 commit comments

Comments
 (0)