LANG-1677 : Add ReflectionDiffBuilder.setExcludeFieldNames(...) and DiffExclude a… (#838)

* LANG-1677 : Make it possible to exclude fields in ReflectionDiffBuilder

* Fix Javadoc typo

* Javadoc

* Javadoc

---------

Co-authored-by: Gary Gregory <garydgregory@users.noreply.github.com>
This commit is contained in:
Baerten Dennis 2023-06-04 20:30:40 +02:00 committed by GitHub
parent 119623760e
commit f51299ced4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 138 additions and 2 deletions

View File

@ -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
}

View File

@ -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<T> implements Builder<DiffResult<T>> {
private final Object right;
private final DiffBuilder<T> 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<T> implements Builder<DiffResult<T>> {
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<T> build() {
if (left.equals(right)) {
@ -126,7 +163,15 @@ public class ReflectionDiffBuilder<T> implements Builder<DiffResult<T>> {
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);
}
}

View File

@ -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<TypeTestClass> 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<TypeTestClass> 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]);
}
}