From 4949adec480ddccdee4f7d09231e85e84fec00a1 Mon Sep 17 00:00:00 2001
From: Gary Gregory
Date: Tue, 12 Dec 2023 07:55:33 -0500
Subject: [PATCH] Add ReflectionDiffBuilder.Builder
- Add ReflectionDiffBuilder.builder()
---
src/changes/changes.xml | 7 +-
.../commons/lang3/builder/DiffBuilder.java | 28 +++-
.../lang3/builder/ReflectionDiffBuilder.java | 158 +++++++++++++-----
.../builder/ReflectionDiffBuilderTest.java | 62 +++++--
4 files changed, 195 insertions(+), 60 deletions(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 304339d08..49aef984e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -50,6 +50,8 @@ The type attribute can be add,update,fix,remove.
Customize text pattern in DiffResult#toString().
Add DiffBuilder.Builder
Add DiffBuilder.builder()
+ Add ReflectionDiffBuilder.Builder
+ Add ReflectionDiffBuilder.builder()
Improve Javadoc in ExceptionUtils #1136.
Fixed two non-deterministic tests in EnumUtilsTest.java #1131.
@@ -62,7 +64,10 @@ The type attribute can be add,update,fix,remove.
Bump commons-parent from 64 to 65.
Drop obsolete JDK 13 Maven profile #1142.
- Deprecate org.apache.commons.lang3.builder.Diff.getType().
+ Deprecate Diff.getType().
+ Deprecate DiffBuilder.DiffBuilder(T, T, ToStringStyle).
+ Deprecate DiffBuilder.DiffBuilder(T, T, ToStringStyle, boolean).
+ Deprecate ReflectionDiffBuilder.ReflectionDiffBuilder(T, T, ToStringStyle).
diff --git a/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java b/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java
index 602ae8be9..378729145 100644
--- a/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java
+++ b/src/main/java/org/apache/commons/lang3/builder/DiffBuilder.java
@@ -32,7 +32,7 @@ import org.apache.commons.lang3.ObjectUtils;
* To use this class, write code as follows:
*
*
- *
+ * {@code
* public class Person implements Diffable<Person> {
* String name;
* int age;
@@ -42,14 +42,18 @@ import org.apache.commons.lang3.ObjectUtils;
*
* public DiffResult diff(Person obj) {
* // No need for null check, as NullPointerException correct if obj is null
- * return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
+ * return new DiffBuilder.builder()
+ * .setLeft(this)
+ * .setRight(obj)
+ * .setStyle(ToStringStyle.SHORT_PREFIX_STYLE))
+ * .build()
* .append("name", this.name, obj.name)
* .append("age", this.age, obj.age)
* .append("smoker", this.smoker, obj.smoker)
* .build();
* }
* }
- *
+ * }
*
*
* The {@link ToStringStyle} passed to the constructor is embedded in the returned {@link DiffResult} and influences the style of the
@@ -574,4 +578,22 @@ public class DiffBuilder implements Builder> {
return new DiffResult<>(left, right, diffs, style, toStringFormat);
}
+ /**
+ * Gets the left object.
+ *
+ * @return the left object.
+ */
+ T getLeft() {
+ return left;
+ }
+
+ /**
+ * Gets the right object.
+ *
+ * @return the right object.
+ */
+ T getRight() {
+ return right;
+ }
+
}
diff --git a/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java b/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java
index 505c21e62..05f08d597 100644
--- a/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java
+++ b/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java
@@ -29,16 +29,14 @@ import org.apache.commons.lang3.reflect.FieldUtils;
* Assists in implementing {@link Diffable#diff(Object)} methods.
*
*
- * All non-static, non-transient fields (including inherited fields)
- * of the objects to diff are discovered using reflection and compared
- * for differences.
+ * All non-static, non-transient fields (including inherited fields) of the objects to diff are discovered using reflection and compared for differences.
*
*
*
* To use this class, write code as follows:
*
*
- *
+ * {@code
* public class Person implements Diffable<Person> {
* String name;
* int age;
@@ -47,23 +45,27 @@ import org.apache.commons.lang3.reflect.FieldUtils;
*
* public DiffResult diff(Person obj) {
* // No need for null check, as NullPointerException correct if obj is null
- * return new ReflectionDiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
+ * return new ReflectionDiffBuilder.builder()
+ * .setDiffBuilder(DiffBuilder.builder()
+ * .setLeft(this)
+ * .setRight(obj)
+ * .setStyle(ToStringStyle.SHORT_PREFIX_STYLE)
+ * .build())
+ * .setExcludeFieldNames("userName", "password")
* .build();
* }
* }
- *
+ * }
*
*
- * The {@link ToStringStyle} passed to the constructor is embedded in the
- * returned {@link DiffResult} and influences the style of the
- * {@code DiffResult.toString()} method. This style choice can be overridden by
- * calling {@link DiffResult#toString(ToStringStyle)}.
+ * The {@link ToStringStyle} passed to the constructor is embedded in the returned {@link DiffResult} and influences the style of the
+ * {@code DiffResult.toString()} method. This style choice can be overridden by calling {@link DiffResult#toString(ToStringStyle)}.
*
*
* See {@link DiffBuilder} for a non-reflection based version of this class.
*
- * @param
- * type of the left and right object to diff.
+ *
+ * @param type of the left and right object to diff.
* @see Diffable
* @see Diff
* @see DiffResult
@@ -73,39 +75,98 @@ import org.apache.commons.lang3.reflect.FieldUtils;
*/
public class ReflectionDiffBuilder implements Builder> {
- private final T left;
- private final T right;
+ /**
+ * Constructs a new instance.
+ *
+ * @param type of the left and right object.
+ * @since 3.15.0
+ */
+ public static final class Builder {
+
+ private String[] excludeFieldNames = ArrayUtils.EMPTY_STRING_ARRAY;
+ private DiffBuilder diffBuilder;
+
+ /**
+ * Builds a new configured {@link ReflectionDiffBuilder}.
+ *
+ * @return a new configured {@link ReflectionDiffBuilder}.
+ */
+ public ReflectionDiffBuilder build() {
+ return new ReflectionDiffBuilder<>(diffBuilder, excludeFieldNames);
+ }
+
+ /**
+ * Sets the DiffBuilder.
+ *
+ * @param diffBuilder the DiffBuilder.
+ * @return this.
+ */
+ public Builder setDiffBuilder(final DiffBuilder diffBuilder) {
+ this.diffBuilder = diffBuilder;
+ return this;
+ }
+
+ /**
+ * Sets field names to exclude from output. Intended for fields like {@code "password"} or {@code "lastModificationDate"}.
+ *
+ * @param excludeFieldNames field names to exclude.
+ * @return this.
+ */
+ public Builder setExcludeFieldNames(final String... excludeFieldNames) {
+ this.excludeFieldNames = toExcludeFieldNames(excludeFieldNames);
+ return this;
+ }
+
+ }
+
+ /**
+ * Constructs a new {@link Builder}.
+ *
+ * @param type of the left and right object.
+ * @return a new {@link Builder}.
+ * @since 3.15.0
+ */
+ public static Builder builder() {
+ return new Builder<>();
+ }
+
+ private static String[] toExcludeFieldNames(final String[] excludeFieldNames) {
+ if (excludeFieldNames == null) {
+ return ArrayUtils.EMPTY_STRING_ARRAY;
+ }
+ // clone and remove nulls
+ return ArraySorter.sort(ReflectionToStringBuilder.toNoNullStringArray(excludeFieldNames));
+ }
+
private final DiffBuilder diffBuilder;
/**
* Field names to exclude from output. Intended for fields like {@code "password"} or {@code "lastModificationDate"}.
- *
- * @since 3.13.0
*/
private String[] excludeFieldNames;
+ private ReflectionDiffBuilder(final DiffBuilder diffBuilder, final String[] excludeFieldNames) {
+ this.diffBuilder = diffBuilder;
+ this.excludeFieldNames = excludeFieldNames;
+ }
+
/**
* Constructs a builder for the specified objects with the specified style.
*
*
- * If {@code lhs == rhs} or {@code lhs.equals(rhs)} then the builder will
- * not evaluate any calls to {@code append(...)} and will return an empty
+ * If {@code left == right} or {@code left.equals(right)} then the builder will not evaluate any calls to {@code append(...)} and will return an empty
* {@link DiffResult} when {@link #build()} is executed.
*
- * @param lhs
- * {@code this} object
- * @param rhs
- * the object to diff against
- * @param style
- * the style will use when outputting the objects, {@code null}
- * uses the default
- * @throws IllegalArgumentException
- * if {@code lhs} or {@code rhs} is {@code null}
+ *
+ * @param left {@code this} object.
+ * @param right the object to diff against.
+ * @param style the style will use when outputting the objects, {@code null} uses the default
+ * @throws IllegalArgumentException if {@code left} or {@code right} is {@code null}.
+ * @deprecated Use {@link Builder}.
*/
- public ReflectionDiffBuilder(final T lhs, final T rhs, final ToStringStyle style) {
- this.left = lhs;
- this.right = rhs;
- this.diffBuilder = DiffBuilder.builder().setLeft(lhs).setRight(rhs).setStyle(style).build();
+ @Deprecated
+ public ReflectionDiffBuilder(final T left, final T right, final ToStringStyle style) {
+ this(DiffBuilder.builder().setLeft(left).setRight(right).setStyle(style).build(), null);
}
private boolean accept(final Field field) {
@@ -118,8 +179,7 @@ public class ReflectionDiffBuilder implements Builder> {
if (Modifier.isStatic(field.getModifiers())) {
return false;
}
- if (this.excludeFieldNames != null
- && Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) {
+ if (this.excludeFieldNames != null && Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) {
// Reject fields from the getExcludeFieldNames list.
return false;
}
@@ -130,7 +190,7 @@ public class ReflectionDiffBuilder implements Builder> {
for (final Field field : FieldUtils.getAllFields(clazz)) {
if (accept(field)) {
try {
- diffBuilder.append(field.getName(), FieldUtils.readField(field, left, true), FieldUtils.readField(field, right, true));
+ diffBuilder.append(field.getName(), readField(field, getLeft()), readField(field, getRight()));
} catch (final IllegalAccessException e) {
// this can't happen. Would get a Security exception instead
// throw a runtime exception in case the impossible happens.
@@ -142,11 +202,11 @@ public class ReflectionDiffBuilder implements Builder> {
@Override
public DiffResult build() {
- if (left.equals(right)) {
+ if (getLeft().equals(getRight())) {
return diffBuilder.build();
}
- appendFields(left.getClass());
+ appendFields(getLeft().getClass());
return diffBuilder.build();
}
@@ -160,21 +220,29 @@ public class ReflectionDiffBuilder implements Builder> {
return this.excludeFieldNames.clone();
}
+ private T getLeft() {
+ return diffBuilder.getLeft();
+ }
+
+ private T getRight() {
+ return diffBuilder.getRight();
+ }
+
+ private Object readField(final Field field, final Object target) throws IllegalAccessException {
+ return FieldUtils.readField(field, target, true);
+ }
+
/**
* Sets the field names to exclude.
*
- * @param excludeFieldNamesParam
- * The field names to exclude from the diff or {@code null}.
+ * @param excludeFieldNames The field names to exclude from the diff or {@code null}.
* @return {@code this}
* @since 3.13.0
+ * @deprecated Use {@link Builder#setExcludeFieldNames(String[])}.
*/
- public ReflectionDiffBuilder setExcludeFieldNames(final String... excludeFieldNamesParam) {
- if (excludeFieldNamesParam == null) {
- this.excludeFieldNames = ArrayUtils.EMPTY_STRING_ARRAY;
- } else {
- // clone and remove nulls
- this.excludeFieldNames = ArraySorter.sort(ReflectionToStringBuilder.toNoNullStringArray(excludeFieldNamesParam));
- }
+ @Deprecated
+ public ReflectionDiffBuilder setExcludeFieldNames(final String... excludeFieldNames) {
+ this.excludeFieldNames = toExcludeFieldNames(excludeFieldNames);
return this;
}
diff --git a/src/test/java/org/apache/commons/lang3/builder/ReflectionDiffBuilderTest.java b/src/test/java/org/apache/commons/lang3/builder/ReflectionDiffBuilderTest.java
index 42c5d6f11..1582d3ade 100644
--- a/src/test/java/org/apache/commons/lang3/builder/ReflectionDiffBuilderTest.java
+++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionDiffBuilderTest.java
@@ -34,23 +34,23 @@ public class ReflectionDiffBuilderTest extends AbstractLangTest {
private static int staticField;
private final ToStringStyle style = SHORT_STYLE;
private final boolean booleanField = true;
- private final boolean[] booleanArrayField = {true};
+ private final boolean[] booleanArrayField = { true };
private final byte byteField = (byte) 0xFF;
- private final byte[] byteArrayField = {(byte) 0xFF};
+ private final byte[] byteArrayField = { (byte) 0xFF };
private char charField = 'a';
- private char[] charArrayField = {'a'};
+ private char[] charArrayField = { 'a' };
private final double doubleField = 1.0;
- private final double[] doubleArrayField = {1.0};
+ private final double[] doubleArrayField = { 1.0 };
private final float floatField = 1.0f;
- private final float[] floatArrayField = {1.0f};
+ private final float[] floatArrayField = { 1.0f };
int intField = 1;
- private final int[] intArrayField = {1};
+ private final int[] intArrayField = { 1 };
private final long longField = 1L;
- private final long[] longArrayField = {1L};
+ private final long[] longArrayField = { 1L };
private final short shortField = 1;
- private final short[] shortArrayField = {1};
+ private final short[] shortArrayField = { 1 };
private final Object objectField = null;
- private final Object[] objectArrayField = {null};
+ private final Object[] objectArrayField = { null };
private transient String transientField;
@DiffExclude
private String annotatedField = "a";
@@ -166,7 +166,26 @@ public class ReflectionDiffBuilderTest extends AbstractLangTest {
@Test
public void testGetExcludeFieldNamesWithNullExcludedFieldNames() {
- final ReflectionDiffBuilder reflectionDiffBuilder = new ReflectionDiffBuilder<>(new TypeTestClass(), new TypeTestChildClass(), SHORT_STYLE);
+ // @formatter:off
+ final ReflectionDiffBuilder reflectionDiffBuilder = ReflectionDiffBuilder.builder()
+ .setDiffBuilder(DiffBuilder.builder()
+ .setLeft(new TypeTestClass())
+ .setRight(new TypeTestChildClass())
+ .setStyle(SHORT_STYLE)
+ .build())
+ .build();
+ // @formatter:on
+ final String[] excludeFieldNames = reflectionDiffBuilder.getExcludeFieldNames();
+ assertNotNull(excludeFieldNames);
+ assertEquals(0, excludeFieldNames.length);
+ }
+
+ @Test
+ public void testGetExcludeFieldNamesWithNullExcludedFieldNamesCtor() {
+ // @formatter:off
+ final ReflectionDiffBuilder reflectionDiffBuilder =
+ new ReflectionDiffBuilder<>(new TypeTestClass(), new TypeTestChildClass(), SHORT_STYLE);
+ // @formatter:on
reflectionDiffBuilder.setExcludeFieldNames(null);
final String[] excludeFieldNames = reflectionDiffBuilder.getExcludeFieldNames();
assertNotNull(excludeFieldNames);
@@ -175,7 +194,28 @@ public class ReflectionDiffBuilderTest extends AbstractLangTest {
@Test
public void testGetExcludeFieldNamesWithNullValuesInExcludedFieldNames() {
- final ReflectionDiffBuilder reflectionDiffBuilder = new ReflectionDiffBuilder<>(new TypeTestClass(), new TypeTestChildClass(), SHORT_STYLE);
+ // @formatter:off
+ final ReflectionDiffBuilder reflectionDiffBuilder = ReflectionDiffBuilder.builder()
+ .setDiffBuilder(DiffBuilder.builder()
+ .setLeft(new TypeTestClass())
+ .setRight(new TypeTestChildClass())
+ .setStyle(SHORT_STYLE)
+ .build())
+ .setExcludeFieldNames("charField", null)
+ .build();
+ // @formatter:on
+ final String[] excludeFieldNames = reflectionDiffBuilder.getExcludeFieldNames();
+ assertNotNull(excludeFieldNames);
+ assertEquals(1, excludeFieldNames.length);
+ assertEquals("charField", excludeFieldNames[0]);
+ }
+
+ @Test
+ public void testGetExcludeFieldNamesWithNullValuesInExcludedFieldNamesCtor() {
+ // @formatter:off
+ final ReflectionDiffBuilder reflectionDiffBuilder =
+ new ReflectionDiffBuilder<>(new TypeTestClass(), new TypeTestChildClass(), SHORT_STYLE);
+ // @formatter:on
reflectionDiffBuilder.setExcludeFieldNames("charField", null);
final String[] excludeFieldNames = reflectionDiffBuilder.getExcludeFieldNames();
assertNotNull(excludeFieldNames);