diff --git a/src/main/java/org/apache/commons/lang3/builder/DiffExclude.java b/src/main/java/org/apache/commons/lang3/builder/DiffExclude.java new file mode 100755 index 000000000..2f69b5776 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/builder/DiffExclude.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.lang3.builder; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excludes a field from being used by + * the {@link ReflectionDiffBuilder}. + * + * @since 3.13.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DiffExclude { + // empty +} 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 ddae64874..1337e93c1 100644 --- a/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java +++ b/src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java @@ -18,7 +18,10 @@ package org.apache.commons.lang3.builder; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.Arrays; +import org.apache.commons.lang3.ArraySorter; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.reflect.FieldUtils; @@ -70,6 +73,12 @@ public class ReflectionDiffBuilder implements Builder> { private final Object right; private final DiffBuilder diffBuilder; + /** + * Which field names to exclude from output. Intended for fields like {@code "password"} or {@code "lastModificationDate"}. + * @since 3.13.0 + */ + private String[] excludeFieldNames; + /** * Constructs a builder for the specified objects with the specified style. * @@ -94,6 +103,34 @@ public class ReflectionDiffBuilder implements Builder> { diffBuilder = new DiffBuilder<>(lhs, rhs, style); } + /** + * Gets the field names that should be excluded from the diff + * @return Returns the excludeFieldNames. + * @since 3.13.0 + */ + public String[] getExcludeFieldNames() { + return this.excludeFieldNames.clone(); + } + + + /** + * Sets the field names to exclude. + * + * @param excludeFieldNamesParam + * The field names to exclude from the diff or {@code null}. + * @return {@code this} + * @since 3.13.0 + */ + 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)); + } + return this; + } + @Override public DiffResult build() { if (left.equals(right)) { @@ -126,7 +163,15 @@ public class ReflectionDiffBuilder implements Builder> { if (Modifier.isTransient(field.getModifiers())) { return false; } - return !Modifier.isStatic(field.getModifiers()); + if (Modifier.isStatic(field.getModifiers())) { + return false; + } + if (this.excludeFieldNames != null + && Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) { + // Reject fields from the getExcludeFieldNames list. + return false; + } + return !field.isAnnotationPresent(DiffExclude.class); } } 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 8fc3a1d43..a1fdce249 100644 --- a/src/test/java/org/apache/commons/lang3/builder/ReflectionDiffBuilderTest.java +++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionDiffBuilderTest.java @@ -17,6 +17,7 @@ package org.apache.commons.lang3.builder; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import org.apache.commons.lang3.AbstractLangTest; import org.junit.jupiter.api.Test; @@ -48,10 +49,14 @@ public class ReflectionDiffBuilderTest extends AbstractLangTest { private final Object[] objectArrayField = {null}; private static int staticField; private transient String transientField; + @DiffExclude + private String annotatedField = "a"; + private String excludedField = "a"; + @Override public DiffResult diff(final TypeTestClass obj) { - return new ReflectionDiffBuilder(this, obj, style).build(); + return new ReflectionDiffBuilder(this, obj, style).setExcludeFieldNames("excludedField").build(); } @Override @@ -128,4 +133,55 @@ public class ReflectionDiffBuilderTest extends AbstractLangTest { final DiffResult list = firstObject.diff(secondObject); assertEquals(1, list.getNumberOfDiffs()); } + + @Test + public void test_no_differences_excluded_field() { + final TypeTestClass firstObject = new TypeTestClass(); + firstObject.excludedField = "b"; + final TypeTestClass secondObject = new TypeTestClass(); + + final DiffResult list = firstObject.diff(secondObject); + assertEquals(0, list.getNumberOfDiffs()); + } + + @Test + public void test_no_differences_diff_exclude_annotated_field() { + final TypeTestClass firstObject = new TypeTestClass(); + firstObject.annotatedField = "b"; + final TypeTestClass secondObject = new TypeTestClass(); + + final DiffResult list = firstObject.diff(secondObject); + assertEquals(0, list.getNumberOfDiffs()); + } + + @Test + public void test_no_differences_diff_exluded_field_and_exclude_annotated_field() { + final TypeTestClass firstObject = new TypeTestClass(); + firstObject.excludedField = "b"; + firstObject.annotatedField = "b"; + final TypeTestClass secondObject = new TypeTestClass(); + + final DiffResult list = firstObject.diff(secondObject); + assertEquals(0, list.getNumberOfDiffs()); + } + + @Test + public void testGetExcludeFieldNamesWithNullExcludedFieldNames() { + final ReflectionDiffBuilder reflectionDiffBuilder = new ReflectionDiffBuilder<>(new TypeTestClass(), new TypeTestChildClass(), SHORT_STYLE); + reflectionDiffBuilder.setExcludeFieldNames(null); + final String[] excludeFieldNames = reflectionDiffBuilder.getExcludeFieldNames(); + assertNotNull(excludeFieldNames); + assertEquals(0, excludeFieldNames.length); + } + + @Test + public void testGetExcludeFieldNamesWithNullValuesInExcludedFieldNames() { + final ReflectionDiffBuilder reflectionDiffBuilder = new ReflectionDiffBuilder<>(new TypeTestClass(), new TypeTestChildClass(), SHORT_STYLE); + reflectionDiffBuilder.setExcludeFieldNames("charField", null); + final String[] excludeFieldNames = reflectionDiffBuilder.getExcludeFieldNames(); + assertNotNull(excludeFieldNames); + assertEquals(1, excludeFieldNames.length); + assertEquals("charField", excludeFieldNames[0]); + } + }