diff --git a/hamcrest/src/main/java/org/hamcrest/beans/HasProperty.java b/hamcrest/src/main/java/org/hamcrest/beans/HasProperty.java
index 8494c9a3..59f351b1 100644
--- a/hamcrest/src/main/java/org/hamcrest/beans/HasProperty.java
+++ b/hamcrest/src/main/java/org/hamcrest/beans/HasProperty.java
@@ -30,8 +30,8 @@ public HasProperty(String propertyName) {
@Override
public boolean matchesSafely(T obj) {
try {
- return PropertyUtil.getPropertyDescriptor(propertyName, obj) != null ||
- PropertyUtil.getMethodDescriptor(propertyName, obj) != null;
+ PropertyAccessor accessor = new PropertyAccessor(obj);
+ return accessor.fieldNames().contains(propertyName);
} catch (IllegalArgumentException e) {
return false;
}
diff --git a/hamcrest/src/main/java/org/hamcrest/beans/HasPropertyWithValue.java b/hamcrest/src/main/java/org/hamcrest/beans/HasPropertyWithValue.java
index f45c1264..c65f460f 100644
--- a/hamcrest/src/main/java/org/hamcrest/beans/HasPropertyWithValue.java
+++ b/hamcrest/src/main/java/org/hamcrest/beans/HasPropertyWithValue.java
@@ -4,10 +4,8 @@
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.hamcrest.beans.PropertyAccessor.PropertyReadLens;
-import java.beans.FeatureDescriptor;
-import java.beans.MethodDescriptor;
-import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
@@ -16,7 +14,7 @@
import static org.hamcrest.Condition.matched;
import static org.hamcrest.Condition.notMatched;
-import static org.hamcrest.beans.PropertyUtil.NO_ARGUMENTS;
+import static org.hamcrest.beans.PropertyAccessor.NO_ARGUMENTS;
/**
*
A matcher that checks if an object has a JavaBean property with the
@@ -71,7 +69,7 @@
*/
public class HasPropertyWithValue extends TypeSafeDiagnosingMatcher {
- private static final Condition.Step WITH_READ_METHOD = withReadMethod();
+ private static final Condition.Step WITH_READ_METHOD = withReadMethod();
private final String propertyName;
private final Matcher valueMatcher;
private final String messageFormat;
@@ -113,17 +111,14 @@ public void describeTo(Description description) {
.appendDescriptionOf(valueMatcher).appendText(")");
}
- private Condition propertyOn(T bean, Description mismatch) {
- FeatureDescriptor property = PropertyUtil.getPropertyDescriptor(propertyName, bean);
- if (property == null) {
- property = PropertyUtil.getMethodDescriptor(propertyName, bean);
- }
- if (property == null) {
+ private Condition propertyOn(T bean, Description mismatch) {
+ PropertyAccessor accessor = new PropertyAccessor(bean);
+ if (!accessor.fieldNames().contains(propertyName)) {
mismatch.appendText("No property \"" + propertyName + "\"");
return notMatched();
}
- return matched(property, mismatch);
+ return matched(accessor.readLensFor(propertyName), mismatch);
}
private Condition.Step withPropertyValue(final T bean) {
@@ -149,13 +144,11 @@ private static Matcher nastyGenericsWorkaround(Matcher> valueMatcher)
return (Matcher) valueMatcher;
}
- private static Condition.Step withReadMethod() {
- return (property, mismatch) -> {
- final Method readMethod = property instanceof PropertyDescriptor ?
- ((PropertyDescriptor) property).getReadMethod() :
- (((MethodDescriptor) property).getMethod());
+ private static Condition.Step withReadMethod() {
+ return (readLens, mismatch) -> {
+ final Method readMethod = readLens.getReadMethod();
if (null == readMethod || readMethod.getReturnType() == void.class) {
- mismatch.appendText("property \"" + property.getName() + "\" is not readable");
+ mismatch.appendText("property \"" + readLens.getName() + "\" is not readable");
return notMatched();
}
return matched(readMethod, mismatch);
diff --git a/hamcrest/src/main/java/org/hamcrest/beans/PropertyAccessor.java b/hamcrest/src/main/java/org/hamcrest/beans/PropertyAccessor.java
new file mode 100644
index 00000000..4dd414cd
--- /dev/null
+++ b/hamcrest/src/main/java/org/hamcrest/beans/PropertyAccessor.java
@@ -0,0 +1,219 @@
+package org.hamcrest.beans;
+
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * Utility class to help with finding properties in an object.
+ *
+ * The properties can be either properties as described by the
+ * JavaBean specification and APIs, or it will fall back to finding
+ * fields with corresponding methods, enabling the property matchers
+ * to work with newer classes like Records.
+ *
+ * See https://docs.oracle.com/javase/8/docs/technotes/guides/beans/index.html for
+ * more information on JavaBeans.
+ */
+public class PropertyAccessor {
+ private final Object beanLikeObject;
+ private final SortedMap readLenses;
+
+ /**
+ * Constructor.
+ * @param beanLikeObject the object to search for properties.
+ */
+ public PropertyAccessor(Object beanLikeObject) {
+ this.beanLikeObject = beanLikeObject;
+ this.readLenses = new TreeMap<>(makeLensesFor(beanLikeObject));
+ }
+
+ private Map makeLensesFor(Object bean) {
+ PropertyDescriptor[] properties = propertyDescriptorsFor(bean, Object.class);
+ if (properties != null && properties.length > 0) {
+ return makePropertyLensesFrom(properties);
+ }
+
+ return makeFieldMethodLensesFor(bean);
+ }
+
+ private Map makePropertyLensesFrom(PropertyDescriptor[] descriptors) {
+ return Arrays.stream(descriptors)
+ .map(pd -> new PropertyReadLens(pd.getDisplayName(), pd.getReadMethod()))
+ .collect(Collectors.toMap(PropertyReadLens::getName, Function.identity()));
+ }
+
+ private Map makeFieldMethodLensesFor(Object bean) {
+ try {
+ Set fieldNames = getFieldNames(bean);
+ MethodDescriptor[] methodDescriptors = Introspector.getBeanInfo(bean.getClass(), null).getMethodDescriptors();
+ return Arrays.stream(methodDescriptors)
+ .filter(IsPropertyAccessor.forOneOf(fieldNames))
+ .map(md -> new PropertyReadLens(md.getDisplayName(), md.getMethod()))
+ .collect(Collectors.toMap(PropertyReadLens::getName, Function.identity()));
+ }
+ catch (IntrospectionException e) {
+ throw new IllegalArgumentException("Could not get method descriptors for " + bean.getClass(), e);
+ }
+ }
+
+ /**
+ * The names of properties that were found in the object.
+ * @return a set of field names
+ */
+ public Set fieldNames() {
+ return readLenses.keySet();
+ }
+
+ /**
+ * The collection of lenses for all the properties that were found in the
+ * object.
+ * @return the collection of lenses
+ */
+ public Collection readLenses() {
+ return readLenses.values();
+ }
+
+ /**
+ * The read lens for the specified property.
+ * @param propertyName the property to find the lens for.
+ * @return the read lens for the property
+ */
+ public PropertyReadLens readLensFor(String propertyName) {
+ return readLenses.get(propertyName);
+ }
+
+ /**
+ * The value of the specified property.
+ * @param propertyName the name of the property
+ * @return the value of the given property name.
+ */
+ public Object fieldValue(String propertyName) {
+ PropertyReadLens lens = readLenses.get(propertyName);
+ if (lens == null) {
+ String message = String.format("Unknown property '%s' for bean '%s'", propertyName, beanLikeObject);
+ throw new IllegalArgumentException(message);
+ }
+ return lens.getValue();
+ }
+
+ /**
+ * Returns the field names of the given object.
+ * It can be the names of the record components of Java Records, for example.
+ *
+ * @param fromObj the object to check
+ * @return The field names
+ * @throws IllegalArgumentException if there's a security issue reading the fields
+ */
+ private static Set getFieldNames(Object fromObj) throws IllegalArgumentException {
+ try {
+ return Arrays.stream(fromObj.getClass().getDeclaredFields())
+ .map(Field::getName)
+ .collect(Collectors.toSet());
+ } catch (SecurityException e) {
+ throw new IllegalArgumentException("Could not get record component names for " + fromObj.getClass(), e);
+ }
+ }
+
+
+ /**
+ * Predicate that checks if a given {@link MethodDescriptor} corresponds to a field.
+ *
+ * This predicate assumes a method is a field access if the method name exactly
+ * matches the field name, takes no parameters and returns a non-void type.
+ */
+ private static class IsPropertyAccessor implements Predicate {
+ private final Set propertyNames;
+
+ private IsPropertyAccessor(Set propertyNames) {
+ this.propertyNames = propertyNames;
+ }
+
+ public static IsPropertyAccessor forOneOf(Set propertyNames) {
+ return new IsPropertyAccessor(propertyNames);
+ }
+
+ @Override
+ public boolean test(MethodDescriptor md) {
+ return propertyNames.contains(md.getDisplayName()) &&
+ md.getMethod().getReturnType() != void.class &&
+ md.getMethod().getParameterCount() == 0;
+ }
+ }
+
+ /**
+ * Encapsulates a property in the parent object.
+ */
+ public class PropertyReadLens {
+ private final String name;
+ private final Method readMethod;
+
+ /**
+ * Constructor.
+ * @param name the name of the property
+ * @param readMethod the method that can be used to get the value of the property
+ */
+ public PropertyReadLens(String name, Method readMethod) {
+ this.name = name;
+ this.readMethod = readMethod;
+ }
+
+ /**
+ * The name of the property
+ * @return the name of the property.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The read method for the property.
+ * @return the read method for the property.
+ */
+ public Method getReadMethod() {
+ return readMethod;
+ }
+
+ /**
+ * The value of the property.
+ * @return the value of the property.
+ */
+ public Object getValue() {
+ Object bean = PropertyAccessor.this.beanLikeObject;
+ try {
+ return readMethod.invoke(bean, NO_ARGUMENTS);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Could not invoke " + readMethod + " on " + bean, e);
+ }
+ }
+ }
+
+ /**
+ * Returns all the property descriptors for the class associated with the given object
+ *
+ * @param fromObj Use the class of this object
+ * @param stopClass Don't include any properties from this ancestor class upwards.
+ * @return Property descriptors
+ * @throws IllegalArgumentException if there's a introspection failure
+ */
+ public static PropertyDescriptor[] propertyDescriptorsFor(Object fromObj, Class stopClass) throws IllegalArgumentException {
+ try {
+ return Introspector.getBeanInfo(fromObj.getClass(), stopClass).getPropertyDescriptors();
+ } catch (IntrospectionException e) {
+ throw new IllegalArgumentException("Could not get property descriptors for " + fromObj.getClass(), e);
+ }
+ }
+
+ /**
+ * Empty object array, used for documenting that we are deliberately passing no arguments to a method.
+ */
+ public static final Object[] NO_ARGUMENTS = new Object[0];
+
+}
diff --git a/hamcrest/src/main/java/org/hamcrest/beans/PropertyUtil.java b/hamcrest/src/main/java/org/hamcrest/beans/PropertyUtil.java
index 40d5a37d..1971ebab 100644
--- a/hamcrest/src/main/java/org/hamcrest/beans/PropertyUtil.java
+++ b/hamcrest/src/main/java/org/hamcrest/beans/PropertyUtil.java
@@ -2,12 +2,7 @@
import java.beans.IntrospectionException;
import java.beans.Introspector;
-import java.beans.MethodDescriptor;
import java.beans.PropertyDescriptor;
-import java.lang.reflect.Field;
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
/**
* Utility class with static methods for accessing properties on JavaBean objects.
@@ -16,8 +11,8 @@
*
* @author Iain McGinniss
* @author Steve Freeman
- * @author Uno Kim
* @since 1.1.0
+ * @deprecated Replaced by {@link PropertyAccessor}
*/
public class PropertyUtil {
@@ -33,7 +28,7 @@ private PropertyUtil() {
* @param fromObj
* the object to check.
* @return the descriptor of the property, or null if the property does not exist.
- * @throws IllegalArgumentException if there's an introspection failure
+ * @throws IllegalArgumentException if there's a introspection failure
*/
public static PropertyDescriptor getPropertyDescriptor(String propertyName, Object fromObj) throws IllegalArgumentException {
for (PropertyDescriptor property : propertyDescriptorsFor(fromObj, null)) {
@@ -51,7 +46,7 @@ public static PropertyDescriptor getPropertyDescriptor(String propertyName, Obje
* @param fromObj Use the class of this object
* @param stopClass Don't include any properties from this ancestor class upwards.
* @return Property descriptors
- * @throws IllegalArgumentException if there's an introspection failure
+ * @throws IllegalArgumentException if there's a introspection failure
*/
public static PropertyDescriptor[] propertyDescriptorsFor(Object fromObj, Class stopClass) throws IllegalArgumentException {
try {
@@ -61,73 +56,6 @@ public static PropertyDescriptor[] propertyDescriptorsFor(Object fromObj, Class<
}
}
- /**
- * Returns the description of the read accessor method with the provided
- * name on the provided object's interface.
- * This is what you need when you try to find a property from a target object
- * when it doesn't follow standard JavaBean specification, a Java Record for example.
- *
- * @param propertyName the object property name.
- * @param fromObj the object to check.
- * @return the descriptor of the method, or null if the method does not exist.
- * @throws IllegalArgumentException if there's an introspection failure
- * @see Java Records
- *
- */
- public static MethodDescriptor getMethodDescriptor(String propertyName, Object fromObj) throws IllegalArgumentException {
- for (MethodDescriptor method : recordReadAccessorMethodDescriptorsFor(fromObj, null)) {
- if (method.getName().equals(propertyName)) {
- return method;
- }
- }
-
- return null;
- }
-
- /**
- * Returns read accessor method descriptors for the class associated with the given object.
- * This is useful when you find getter methods for the fields from the object
- * when it doesn't follow standard JavaBean specification, a Java Record for example.
- * Be careful as this doesn't return standard JavaBean getter methods, like a method starting with {@code get-}.
- *
- * @param fromObj Use the class of this object
- * @param stopClass Don't include any properties from this ancestor class upwards.
- * @return Method descriptors for read accessor methods
- * @throws IllegalArgumentException if there's an introspection failure
- */
- public static MethodDescriptor[] recordReadAccessorMethodDescriptorsFor(Object fromObj, Class stopClass) throws IllegalArgumentException {
- try {
- Set recordComponentNames = getFieldNames(fromObj);
- MethodDescriptor[] methodDescriptors = Introspector.getBeanInfo(fromObj.getClass(), stopClass).getMethodDescriptors();
-
- return Arrays.stream(methodDescriptors)
- .filter(x -> recordComponentNames.contains(x.getDisplayName()))
- .filter(x -> x.getMethod().getReturnType() != void.class)
- .filter(x -> x.getMethod().getParameterCount() == 0)
- .toArray(MethodDescriptor[]::new);
- } catch (IntrospectionException e) {
- throw new IllegalArgumentException("Could not get method descriptors for " + fromObj.getClass(), e);
- }
- }
-
- /**
- * Returns the field names of the given object.
- * It can be the names of the record components of Java Records, for example.
- *
- * @param fromObj the object to check
- * @return The field names
- * @throws IllegalArgumentException if there's a security issue reading the fields
- */
- public static Set getFieldNames(Object fromObj) throws IllegalArgumentException {
- try {
- return Arrays.stream(fromObj.getClass().getDeclaredFields())
- .map(Field::getName)
- .collect(Collectors.toSet());
- } catch (SecurityException e) {
- throw new IllegalArgumentException("Could not get record component names for " + fromObj.getClass(), e);
- }
- }
-
/**
* Empty object array, used for documenting that we are deliberately passing no arguments to a method.
*/
diff --git a/hamcrest/src/main/java/org/hamcrest/beans/SamePropertyValuesAs.java b/hamcrest/src/main/java/org/hamcrest/beans/SamePropertyValuesAs.java
index fbb0175f..58425eee 100644
--- a/hamcrest/src/main/java/org/hamcrest/beans/SamePropertyValuesAs.java
+++ b/hamcrest/src/main/java/org/hamcrest/beans/SamePropertyValuesAs.java
@@ -3,17 +3,13 @@
import org.hamcrest.Description;
import org.hamcrest.DiagnosingMatcher;
import org.hamcrest.Matcher;
+import org.hamcrest.beans.PropertyAccessor.PropertyReadLens;
import java.beans.FeatureDescriptor;
-import java.beans.MethodDescriptor;
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.Method;
import java.util.*;
+import java.util.stream.Collectors;
import static java.util.Arrays.asList;
-import static org.hamcrest.beans.PropertyUtil.NO_ARGUMENTS;
-import static org.hamcrest.beans.PropertyUtil.propertyDescriptorsFor;
-import static org.hamcrest.beans.PropertyUtil.recordReadAccessorMethodDescriptorsFor;
import static org.hamcrest.core.IsEqual.equalTo;
/**
@@ -36,15 +32,11 @@ public class SamePropertyValuesAs extends DiagnosingMatcher {
*/
@SuppressWarnings("WeakerAccess")
public SamePropertyValuesAs(T expectedBean, List ignoredProperties) {
- FeatureDescriptor[] descriptors = propertyDescriptorsFor(expectedBean, Object.class);
- if (descriptors == null || descriptors.length == 0) {
- descriptors = recordReadAccessorMethodDescriptorsFor(expectedBean, Object.class);
- }
-
+ PropertyAccessor accessor = new PropertyAccessor(expectedBean);
this.expectedBean = expectedBean;
this.ignoredFields = ignoredProperties;
- this.propertyNames = propertyNamesFrom(descriptors, ignoredProperties);
- this.propertyMatchers = propertyMatchersFor(expectedBean, descriptors, ignoredProperties);
+ this.propertyNames = propertyNamesFrom(accessor, ignoredProperties);
+ this.propertyMatchers = propertyMatchersFor(expectedBean, accessor, ignoredProperties);
}
@Override
@@ -75,8 +67,9 @@ private boolean isCompatibleType(Object actual, Description mismatchDescription)
}
private boolean hasNoExtraProperties(Object actual, Description mismatchDescription) {
- Set actualPropertyNames = propertyNamesFrom(propertyDescriptorsFor(actual, Object.class), ignoredFields);
- actualPropertyNames.removeAll(propertyNames);
+ PropertyAccessor accessor = new PropertyAccessor(actual);
+ Set actualPropertyNames = propertyNamesFrom(accessor, ignoredFields);
+ propertyNames.forEach(actualPropertyNames::remove);
if (!actualPropertyNames.isEmpty()) {
mismatchDescription.appendText("has extra properties called " + actualPropertyNames);
return false;
@@ -94,24 +87,17 @@ private boolean hasMatchingValues(Object actual, Description mismatchDescription
return true;
}
- private static List propertyMatchersFor(T bean, FeatureDescriptor[] descriptors, List ignoredFields) {
- List result = new ArrayList<>(descriptors.length);
- for (FeatureDescriptor descriptor : descriptors) {
- if (isNotIgnored(ignoredFields, descriptor)) {
- result.add(new PropertyMatcher(descriptor, bean));
- }
- }
- return result;
+ private static List propertyMatchersFor(T bean, PropertyAccessor accessor, List ignoredFields) {
+ return accessor.readLenses().stream()
+ .filter(lens -> !ignoredFields.contains(lens.getName()))
+ .map(lens -> new PropertyMatcher(lens, bean))
+ .collect(Collectors.toList());
}
- private static Set propertyNamesFrom(FeatureDescriptor[] descriptors, List ignoredFields) {
- HashSet result = new HashSet<>();
- for (FeatureDescriptor descriptor : descriptors) {
- if (isNotIgnored(ignoredFields, descriptor)) {
- result.add(descriptor.getDisplayName());
- }
- }
- return result;
+ private static Set propertyNamesFrom(PropertyAccessor accessor, List ignoredFields) {
+ Set fieldNames = new HashSet<>(accessor.fieldNames());
+ ignoredFields.forEach(fieldNames::remove);
+ return fieldNames;
}
private static boolean isNotIgnored(List ignoredFields, FeatureDescriptor propertyDescriptor) {
@@ -120,23 +106,20 @@ private static boolean isNotIgnored(List ignoredFields, FeatureDescripto
@SuppressWarnings("WeakerAccess")
private static class PropertyMatcher extends DiagnosingMatcher {
- private final Method readMethod;
+ private final PropertyReadLens expectedReadLens;
private final Matcher matcher;
- private final String propertyName;
-
- public PropertyMatcher(FeatureDescriptor descriptor, Object expectedObject) {
- this.propertyName = descriptor.getDisplayName();
- this.readMethod = descriptor instanceof PropertyDescriptor ?
- ((PropertyDescriptor) descriptor).getReadMethod() :
- ((MethodDescriptor) descriptor).getMethod();
- this.matcher = equalTo(readProperty(readMethod, expectedObject));
+
+ public PropertyMatcher(PropertyReadLens expectedReadLens, Object expectedObject) {
+ this.expectedReadLens = expectedReadLens;
+ this.matcher = equalTo(expectedReadLens.getValue());
}
@Override
public boolean matches(Object actual, Description mismatch) {
- final Object actualValue = readProperty(readMethod, actual);
+ PropertyAccessor actualAccessor = new PropertyAccessor(actual);
+ Object actualValue = actualAccessor.fieldValue(expectedReadLens.getName());
if (!matcher.matches(actualValue)) {
- mismatch.appendText(propertyName + " ");
+ mismatch.appendText(expectedReadLens.getName()+ " ");
matcher.describeMismatch(actualValue, mismatch);
return false;
}
@@ -145,15 +128,7 @@ public boolean matches(Object actual, Description mismatch) {
@Override
public void describeTo(Description description) {
- description.appendText(propertyName + ": ").appendDescriptionOf(matcher);
- }
- }
-
- private static Object readProperty(Method method, Object target) {
- try {
- return method.invoke(target, NO_ARGUMENTS);
- } catch (Exception e) {
- throw new IllegalArgumentException("Could not invoke " + method + " on " + target, e);
+ description.appendText(expectedReadLens.getName() + ": ").appendDescriptionOf(matcher);
}
}
diff --git a/hamcrest/src/test/java/org/hamcrest/beans/HasPropertyWithValueTest.java b/hamcrest/src/test/java/org/hamcrest/beans/HasPropertyWithValueTest.java
index a7dcc00d..ca538cde 100644
--- a/hamcrest/src/test/java/org/hamcrest/beans/HasPropertyWithValueTest.java
+++ b/hamcrest/src/test/java/org/hamcrest/beans/HasPropertyWithValueTest.java
@@ -1,6 +1,8 @@
package org.hamcrest.beans;
-import org.hamcrest.*;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.StringDescription;
import org.hamcrest.core.IsEqual;
import org.hamcrest.test.AbstractMatcherTest;
import org.junit.jupiter.api.Test;
@@ -11,12 +13,12 @@
import java.util.Objects;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.test.MatcherAssertions.*;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
import static org.hamcrest.beans.HasPropertyWithValue.hasPropertyAtPath;
import static org.hamcrest.core.IsAnything.anything;
import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.test.MatcherAssertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
@@ -75,9 +77,9 @@ public void testMatchesRecordLikeBeanWithoutInfoWithMatchedNamedBooleanProperty(
@Test
public void testMatchesBeanWithInfoWithMatchedNamedProperty() {
- assertMatches("with bean info", hasProperty("property", equalTo("with info")), beanWithInfo);
+ assertMatches(hasProperty("property", equalTo("with info")), beanWithInfo);
assertMismatchDescription("property 'property' was \"with info\"",
- hasProperty("property", equalTo("without info")), beanWithInfo);
+ hasProperty("property", equalTo("different")), beanWithInfo);
}
@Test
@@ -253,7 +255,10 @@ public static class BeanWithInfo {
public String property() { return propertyValue; }
}
- public static class BeanWithInfoBeanInfo extends SimpleBeanInfo { // TODO: No usage. Can be removed.
+ /**
+ * Used by bean introspector
+ */
+ public static class BeanWithInfoBeanInfo extends SimpleBeanInfo {
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
try {
diff --git a/hamcrest/src/test/java/org/hamcrest/beans/PropertyUtilTest.java b/hamcrest/src/test/java/org/hamcrest/beans/PropertyAccessorTest.java
similarity index 59%
rename from hamcrest/src/test/java/org/hamcrest/beans/PropertyUtilTest.java
rename to hamcrest/src/test/java/org/hamcrest/beans/PropertyAccessorTest.java
index bc9ba6a0..cfed2185 100644
--- a/hamcrest/src/test/java/org/hamcrest/beans/PropertyUtilTest.java
+++ b/hamcrest/src/test/java/org/hamcrest/beans/PropertyAccessorTest.java
@@ -2,71 +2,54 @@
import org.junit.jupiter.api.Test;
-import java.beans.MethodDescriptor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
-import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.hasItem;
-class PropertyUtilTest {
+class PropertyAccessorTest {
@Test
- void testReturnsTheNamesOfAllFieldsFromTargetClass() {
+ void testAccessesAllFieldsFromBean() {
SamePropertyValuesAsTest.ExampleBean input = new SamePropertyValuesAsTest.ExampleBean("test", 1, null);
+ PropertyAccessor accessor = new PropertyAccessor(input);
- Set output = PropertyUtil.getFieldNames(input);
+ Set fields = accessor.fieldNames();
- assertThat(output, hasSize(3));
- assertThat(output, hasItems("stringProperty", "intProperty", "valueProperty"));
- assertThat(output, not(hasItem("nonexistentField")));
- }
-
- @Test
- void testReturnsTheNamesOfAllFieldsFromTargetRecord() {
- RecordLikeClass.SmallClass smallClass1 = new RecordLikeClass.SmallClass();
- RecordLikeClass.SmallClass smallClass2 = new RecordLikeClass.SmallClass("small", 3, BigDecimal.ONE, LocalDateTime.of(2024, 1, 2, 3, 4, 5));
- RecordLikeClass input = new RecordLikeClass("uno", 22, true, new Long[] {1L, 2L, 3L}, new ArrayList<>(Arrays.asList(smallClass1, smallClass2)));
-
- Set output = PropertyUtil.getFieldNames(input);
-
- assertThat(output, hasSize(5));
- assertThat(output, hasItems("numberArray", "test", "smallClasses", "name", "age"));
- assertThat(output, not(hasItem("notAGetter1")));
- assertThat(output, not(hasItem("notAGetter2")));
- assertThat(output, not(hasItem("getAge")));
- assertThat(output, not(hasItem("field1")));
- assertThat(output, not(hasItem("nonexistentField")));
- }
-
- @Test
- void testReturnsArrayOfMethodDescriptorFromTargetClass() {
- SamePropertyValuesAsTest.ExampleBean input = new SamePropertyValuesAsTest.ExampleBean("test", 1, null);
+ assertThat(fields, hasSize(3));
+ assertThat(fields, hasItems("stringProperty", "intProperty", "valueProperty"));
+ assertThat(fields, not(hasItem("nonexistentField")));
- MethodDescriptor[] output = PropertyUtil.recordReadAccessorMethodDescriptorsFor(input, Object.class);
-
- assertThat(output, arrayWithSize(0));
+ assertThat(accessor.fieldValue("stringProperty"), equalTo("test"));
+ assertThat(accessor.fieldValue("intProperty"), equalTo(1));
+ assertThat(accessor.fieldValue("valueProperty"), equalTo(null));
}
@Test
- void testReturnsArrayOfMethodDescriptorFromTargetRecord() {
+ void testReturnsTheNamesOfAllFieldsFromRecordLikeObject() {
RecordLikeClass.SmallClass smallClass1 = new RecordLikeClass.SmallClass();
RecordLikeClass.SmallClass smallClass2 = new RecordLikeClass.SmallClass("small", 3, BigDecimal.ONE, LocalDateTime.of(2024, 1, 2, 3, 4, 5));
- RecordLikeClass input = new RecordLikeClass("uno", 22, true, new Long[] {1L, 2L, 3L}, new ArrayList<>(Arrays.asList(smallClass1, smallClass2)));
+ RecordLikeClass input = new RecordLikeClass("uno", 22, true, new Long[] {1L, 2L, 3L}, Arrays.asList(smallClass1, smallClass2));
+ PropertyAccessor accessor = new PropertyAccessor(input);
- MethodDescriptor[] output = PropertyUtil.recordReadAccessorMethodDescriptorsFor(input, Object.class);
+ Set fields = accessor.fieldNames();
- assertThat(output, arrayWithSize(5));
- assertThat(Arrays.stream(output).map(MethodDescriptor::getDisplayName).collect(Collectors.toList()),
- hasItems("numberArray", "test", "smallClasses", "name", "age"));
- }
+ assertThat(fields, hasSize(5));
+ assertThat(fields, hasItems("numberArray", "test", "smallClasses", "name", "age"));
+ assertThat(fields, not(hasItem("notAGetter1")));
+ assertThat(fields, not(hasItem("notAGetter2")));
+ assertThat(fields, not(hasItem("field1")));
+ assertThat(fields, not(hasItem("nonexistentField")));
+ assertThat(accessor.fieldValue("name"), equalTo("uno"));
+ }
/**
* A Java Record-like class to test the functionality of
- * {@link PropertyUtil} with Java Records in JDK 8 environment.
+ * {@link PropertyAccessor} with Java Records in JDK 8 environment.
*
* @see https://docs.oracle.com/en/java/javase/17/language/records.html
*/
@@ -76,9 +59,9 @@ static final class RecordLikeClass {
private final int age;
private final boolean test;
private final Long[] numberArray;
- private final List smallClasses;
+ private final List smallClasses;
- public RecordLikeClass(String name, int age, boolean test, Long[] numberArray, List smallClasses) {
+ public RecordLikeClass(String name, int age, boolean test, Long[] numberArray, List smallClasses) {
this.name = name;
this.age = age;
this.test = test;
@@ -90,13 +73,12 @@ public RecordLikeClass(String name, int age, boolean test, Long[] numberArray, L
public int age() { return age; }
public boolean test() { return test; }
public Long[] numberArray() { return numberArray; }
- public List smallClasses() { return smallClasses; }
+ public List smallClasses() { return smallClasses; }
public void notAGetter1() {}
public String notAGetter2() { return "I'm nothing"; }
public String name(String fake1, String fake2) { return name; }
public void name(String fake1) {}
- public int getAge() { return 0; }
@Override
public boolean equals(Object o) {
@@ -147,4 +129,4 @@ public String toString() {
}
}
-}
+}
\ No newline at end of file
diff --git a/hamcrest/src/test/java/org/hamcrest/beans/SamePropertyValuesAsTest.java b/hamcrest/src/test/java/org/hamcrest/beans/SamePropertyValuesAsTest.java
index bd5f1796..7df26392 100644
--- a/hamcrest/src/test/java/org/hamcrest/beans/SamePropertyValuesAsTest.java
+++ b/hamcrest/src/test/java/org/hamcrest/beans/SamePropertyValuesAsTest.java
@@ -107,14 +107,14 @@ public void testDescribesItself() {
"same property values as ExampleBean [intProperty: <1>, stringProperty: \"same\", valueProperty: ]",
samePropertyValuesAs(expectedBean));
assertDescription(
- "same property values as ExampleRecord [valueProperty: , stringProperty: \"same\", intProperty: <1>]",
+ "same property values as ExampleRecord [intProperty: <1>, stringProperty: \"same\", valueProperty: ]",
samePropertyValuesAs(expectedRecord));
assertDescription(
"same property values as ExampleBean [intProperty: <1>, stringProperty: \"same\", valueProperty: ] ignoring [\"ignored1\", \"ignored2\"]",
samePropertyValuesAs(expectedBean, "ignored1", "ignored2"));
assertDescription(
- "same property values as ExampleRecord [valueProperty: , stringProperty: \"same\", intProperty: <1>] ignoring [\"ignored1\", \"ignored2\"]",
+ "same property values as ExampleRecord [intProperty: <1>, stringProperty: \"same\", valueProperty: ] ignoring [\"ignored1\", \"ignored2\"]",
samePropertyValuesAs(expectedRecord, "ignored1", "ignored2"));
}