Skip to content

Commit d073b24

Browse files
committed
Added support for bean-like methods defined on interfaces
1 parent 2c51c71 commit d073b24

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

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

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
import java.util.List;
1818
import java.util.Map;
1919
import java.util.Set;
20+
import java.util.regex.Pattern;
21+
22+
import static java.util.regex.Pattern.compile;
2023

2124
/**
2225
* A {@link Field} shorthand utility class used to collect fields from classes meeting Java Bean restrictions/requirements.
@@ -36,7 +39,7 @@
3639
*/
3740
@UtilityClass
3841
public final class BeanUtils {
39-
42+
4043
/**
4144
* Determines what visibility modifiers a field is allowed to have in {@link BeanUtils#collectFields(Class, Class, EnumSet, EnumSet)}.
4245
*/
@@ -89,7 +92,11 @@ public enum BeanRestriction {
8992
}
9093

9194
/**
92-
* Verifies is a given method occurs as setter or getter in the declaring class chain.
95+
* Verifies is a given method occurs as setter or getter in the declaring class chain. Lookup works by finding actual properties with
96+
* their respective getters/setters that follow bean convention.
97+
* <p>
98+
* Note that this is a strict lookup and interface methods are not considered bean methods. To include interfaces and their methods,
99+
* use {@link #isBeanMethod(Method, Class, EnumSet, boolean)} with <em>checkBeanLikeForInterfaces</em> set to {@code true}.
93100
* <p>
94101
* Lookup can be configured to check only against specific visibility.
95102
*
@@ -103,6 +110,20 @@ public enum BeanRestriction {
103110
@SuppressWarnings({"unused", "WeakerAccess"})
104111
public static boolean isBeanMethod(final Method method, final Class<?> boundaryMarker,
105112
final EnumSet<Visibility> visibility) {
113+
return isBeanMethod(method, boundaryMarker, visibility, false);
114+
}
115+
116+
/**
117+
* @return Same as {@link #isBeanMethod(Method, Class, EnumSet)}, but may consider methods declared on interfaces as well.
118+
*/
119+
private static boolean isBeanMethod(Method method, Class<?> boundaryMarker,
120+
EnumSet<Visibility> visibility, boolean checkBeanLikeForInterfaces) {
121+
return method.getDeclaringClass().isInterface()
122+
? checkBeanLikeForInterfaces && methodIsBeanlike(method)
123+
: isBeanMethodForField(method, boundaryMarker, visibility);
124+
}
125+
126+
private static boolean isBeanMethodForField(Method method, Class<?> boundaryMarker, EnumSet<Visibility> visibility) {
106127
Map<Class<?>, List<FieldWrapper>> fields = collectFields(method.getDeclaringClass(), boundaryMarker, visibility,
107128
EnumSet.noneOf(BeanRestriction.class));
108129
for (List<FieldWrapper> fieldWrappers : fields.values()) {
@@ -114,7 +135,29 @@ public static boolean isBeanMethod(final Method method, final Class<?> boundaryM
114135
}
115136
return false;
116137
}
117-
138+
139+
/**
140+
* Determines if the method <em>could</em> be a bean method by looking just at its name, parameters and presence of return type.
141+
*
142+
* @return True, is the method starts with set/get/is, has exactly one parameter and in case of
143+
* a primitive boolean the method should start with "isAbc"
144+
*/
145+
@SuppressWarnings("WeakerAccess")
146+
public static boolean methodIsBeanlike(Method method) {
147+
final Pattern SET_PATTERN = compile("set[A-Z].*?");
148+
final Pattern GET_PATTERN = compile("get[A-Z].*?");
149+
final Pattern IS_PATTERN = compile("is[A-Z].*?");
150+
151+
final String name = method.getName();
152+
final int paramCount = method.getParameterTypes().length;
153+
final Class<?> rt = method.getReturnType();
154+
155+
return
156+
(rt == boolean.class && IS_PATTERN.matcher(name).matches() && paramCount == 0) ||
157+
(rt != void.class && rt != boolean.class && GET_PATTERN.matcher(name).matches() && paramCount == 0) ||
158+
(rt == void.class && SET_PATTERN.matcher(name).matches() && paramCount == 1);
159+
}
160+
118161
/**
119162
* Returns a pool of {@link Field} wrappers including optional relevant setter/getter methods, collected from the given class tested
120163
* against the given visibility and Bean restriction requirements.

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,4 +354,41 @@ public void setPrimitiveBoolean(final boolean primitiveBoolean) {
354354
this.primitiveBoolean = primitiveBoolean;
355355
}
356356
}
357+
358+
@Test
359+
public void testMethodIsBeanlike() {
360+
for (Method method : ValidBeanlikeMethods.class.getMethods()) {
361+
assertThat(BeanUtils.methodIsBeanlike(method)).describedAs("method IS a beanlike: %s", method).isTrue();
362+
}
363+
for (Method method : InvalidBeanlikeMethods.class.getMethods()) {
364+
assertThat(BeanUtils.methodIsBeanlike(method)).describedAs("method IS NOT beanlike: %s", method).isFalse();
365+
}
366+
}
367+
368+
@SuppressWarnings("unused")
369+
public interface ValidBeanlikeMethods {
370+
// valid beanlike setters
371+
void setBooleanPrimitive(boolean b);
372+
void setBooleanBoxed(Boolean b);
373+
void setObject(Object b);
374+
void setInteger(Integer b);
375+
// valid beanlike getters
376+
boolean isBooleanPrimitive();
377+
Boolean getBooleanBoxed();
378+
Object getObject();
379+
Integer getInteger();
380+
}
381+
382+
@SuppressWarnings("unused")
383+
public interface InvalidBeanlikeMethods {
384+
// invalid beanlike setters
385+
boolean setBooleanPrimitive(boolean b);
386+
Object setBooleanBoxed(Boolean b);
387+
void setBooleanBoxed(Boolean b, Boolean b2);
388+
void setObject();
389+
// invalid beanlike getters
390+
Boolean isBooleanPrimitive();
391+
Boolean getBooleanBoxed(boolean b);
392+
void getObject();
393+
}
357394
}

0 commit comments

Comments
 (0)