Skip to content

Commit 1360ddc

Browse files
committed
Merge branch 'master' of ssh://github.com/ExpediaGroup/bull
2 parents 1a0e614 + 6519502 commit 1360ddc

16 files changed

Lines changed: 216 additions & 11 deletions

File tree

CHANGELOG-JDK11.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
### [2.1.6-jdk11] 2026.03.31
6+
* Fixes a regression introduced in 2.1.5-jdk11 where field transformers applied to collection elements were incorrectly resolved against the root source object instead of the current element, causing a NullPointerException when a flat field name mapping existed with the same name as the destination field
7+
58
### [2.1.5-jdk11] 2026.03.30
69
* Fixes an issue that was preventing the transformation of Kotlin classes with default parameter values
710
* Fixes MissingFieldException when mapping a root-level primitive field into a nested destination object via `withFieldMapping` (issue #559)

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
### [3.0.2] 2026.03.31
6+
* Fixes a regression introduced in 3.0.1 where field transformers applied to collection elements were incorrectly resolved against the root source object instead of the current element, causing a NullPointerException when a flat field name mapping existed with the same name as the destination field
7+
58
### [3.0.1] 2026.03.30
69
* Fixes an issue that was preventing the transformation of Kotlin classes with default parameter values
710
* Fixes MissingFieldException when mapping a root-level primitive field into a nested destination object via `withFieldMapping` (issue #559)

bull-bean-transformer/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<parent>
99
<groupId>com.expediagroup.beans</groupId>
1010
<artifactId>bean-utils-library-parent</artifactId>
11-
<version>3.0.2-SNAPSHOT</version>
11+
<version>3.0.3-SNAPSHOT</version>
1212
</parent>
1313

1414
<dependencies>

bull-bean-transformer/src/main/java/com/expediagroup/beans/transformer/TransformerImpl.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,11 @@ private static final class EffectiveSource<T> {
9393
@SuppressWarnings("unchecked")
9494
private <T> EffectiveSource<T> resolveEffectiveSource(final T sourceObj, final String destFieldName, final String breadcrumb) {
9595
String fieldBreadcrumb = evalBreadcrumb(destFieldName, breadcrumb);
96-
String mapped = settings.getFieldsNameMapping().get(fieldBreadcrumb);
97-
if (mapped != null && rootSourceObj.get() != null) {
98-
return new EffectiveSource<>((T) rootSourceObj.get(), mapped);
96+
if (isNotEmpty(breadcrumb)) {
97+
String mapped = settings.getFieldsNameMapping().get(fieldBreadcrumb);
98+
if (mapped != null && rootSourceObj.get() != null) {
99+
return new EffectiveSource<>((T) rootSourceObj.get(), mapped);
100+
}
99101
}
100102
return new EffectiveSource<>(sourceObj, getSourceFieldName(destFieldName));
101103
}

bull-bean-transformer/src/test/java/com/expediagroup/beans/transformer/AbstractBeanTransformerTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright (C) 2019-2023 Expedia, Inc.
2+
* Copyright (C) 2019-2026 Expedia, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,5 +55,6 @@ void beforeMethod() {
5555
openMocks(this);
5656
underTest.resetFieldsTransformer();
5757
underTest.resetFieldsTransformationSkip();
58+
underTest.resetFieldsMapping();
5859
}
5960
}

bull-bean-transformer/src/test/java/com/expediagroup/beans/transformer/MixedObjectTransformationTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,23 @@
1717

1818
import static java.math.BigInteger.ONE;
1919

20+
import static org.apache.commons.lang3.StringUtils.SPACE;
2021
import static org.assertj.core.api.Assertions.assertThat;
2122

2223
import java.math.BigInteger;
2324
import java.util.List;
2425

2526
import org.testng.annotations.Test;
2627

28+
import com.expediagroup.beans.sample.FromFooContainingList;
2729
import com.expediagroup.beans.sample.FromFooSimple;
2830
import com.expediagroup.beans.sample.FromFooSimpleNested;
31+
import com.expediagroup.beans.sample.FromFooWithFullName;
2932
import com.expediagroup.beans.sample.FromFooWithPrimitiveAndNestedObject;
3033
import com.expediagroup.beans.sample.ToFooNestedWithDiffFieldName;
3134
import com.expediagroup.beans.sample.ToFooWithNestedFieldMapping;
35+
import com.expediagroup.beans.sample.immutable.ImmutableToFooContainingList;
36+
import com.expediagroup.beans.sample.immutable.ImmutableToFooWithSplitName;
3237
import com.expediagroup.beans.sample.mixed.MixedToFoo;
3338
import com.expediagroup.beans.sample.mixed.MixedToFooDiffFields;
3439
import com.expediagroup.beans.sample.mixed.MixedToFooMissingAllArgsConstructor;
@@ -385,4 +390,65 @@ public void testMultipleRootFieldsMappedIntoSameNestedObject() {
385390
assertThat(actual.getNestedObject().getName()).isEqualTo("Lucas");
386391
underTest.reset();
387392
}
393+
394+
/**
395+
* Test that a field transformer applied with flat field name transformation correctly uses
396+
* the collection element as the source, not the root object.
397+
* Regression test: in 3.0.1, resolveEffectiveSource matched the flat mapping ("name" -> "fullName")
398+
* when the breadcrumb was null (collection element path), redirecting the source lookup to the
399+
* root object which had no "fullName" field. The silenced MissingFieldException left the value
400+
* as null, causing an NPE in the transformer function.
401+
*/
402+
@Test
403+
public void testFieldTransformerOnCollectionElementsUsesElementNotRootAsSource() {
404+
// GIVEN
405+
FromFooContainingList source = new FromFooContainingList("root-id",
406+
List.of(new FromFooWithFullName("Donald Duck"), new FromFooWithFullName("Daisy Duck")));
407+
408+
// WHEN
409+
ImmutableToFooContainingList actual = underTest
410+
.setFlatFieldNameTransformation(true)
411+
.withFieldMapping(new FieldMapping<>("fullName", "name"))
412+
.withFieldMapping(new FieldMapping<>("fullName", "surname"))
413+
.withFieldTransformer(new FieldTransformer<String, String>("name", fullName -> fullName.split(SPACE)[0]))
414+
.withFieldTransformer(new FieldTransformer<String, String>("surname", fullName -> fullName.split(SPACE)[1]))
415+
.transform(source, ImmutableToFooContainingList.class);
416+
417+
// THEN
418+
assertThat(actual.getId()).isEqualTo("root-id");
419+
assertThat(actual.getItems()).hasSize(2);
420+
assertThat(actual.getItems().get(0).getName()).isEqualTo("Donald");
421+
assertThat(actual.getItems().get(0).getSurname()).isEqualTo("Duck");
422+
assertThat(actual.getItems().get(1).getName()).isEqualTo("Daisy");
423+
assertThat(actual.getItems().get(1).getSurname()).isEqualTo("Duck");
424+
underTest.reset();
425+
}
426+
427+
/**
428+
* Test that multiple items in a collection are all correctly transformed using their own
429+
* element as the source, not the root. Verifies no cross-contamination between elements.
430+
*/
431+
@Test
432+
public void testAllCollectionElementsAreIndependentlyTransformedFromTheirOwnSource() {
433+
// GIVEN
434+
FromFooContainingList source = new FromFooContainingList("root-id",
435+
List.of(new FromFooWithFullName("Tony Stark"), new FromFooWithFullName("Bruce Banner"),
436+
new FromFooWithFullName("Steve Rogers")));
437+
438+
// WHEN
439+
ImmutableToFooContainingList actual = underTest
440+
.setFlatFieldNameTransformation(true)
441+
.withFieldMapping(new FieldMapping<>("fullName", "name"))
442+
.withFieldMapping(new FieldMapping<>("fullName", "surname"))
443+
.withFieldTransformer(new FieldTransformer<String, String>("name", fullName -> fullName.split(SPACE)[0]))
444+
.withFieldTransformer(new FieldTransformer<String, String>("surname", fullName -> fullName.split(SPACE)[1]))
445+
.transform(source, ImmutableToFooContainingList.class);
446+
447+
// THEN
448+
assertThat(actual.getItems()).extracting(ImmutableToFooWithSplitName::getName)
449+
.containsExactly("Tony", "Bruce", "Steve");
450+
assertThat(actual.getItems()).extracting(ImmutableToFooWithSplitName::getSurname)
451+
.containsExactly("Stark", "Banner", "Rogers");
452+
underTest.reset();
453+
}
388454
}

bull-bom/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<parent>
1010
<groupId>com.expediagroup.beans</groupId>
1111
<artifactId>bean-utils-library-parent</artifactId>
12-
<version>3.0.2-SNAPSHOT</version>
12+
<version>3.0.3-SNAPSHOT</version>
1313
</parent>
1414

1515
<dependencyManagement>

bull-common/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<parent>
1010
<groupId>com.expediagroup.beans</groupId>
1111
<artifactId>bean-utils-library-parent</artifactId>
12-
<version>3.0.2-SNAPSHOT</version>
12+
<version>3.0.3-SNAPSHOT</version>
1313
</parent>
1414

1515
<dependencies>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Copyright (C) 2019-2026 Expedia, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.expediagroup.beans.sample;
17+
18+
import java.util.List;
19+
20+
import lombok.AllArgsConstructor;
21+
import lombok.Getter;
22+
import lombok.ToString;
23+
24+
/**
25+
* Source bean containing a list of items with a full name field.
26+
* Used to test that flat field transformers on collection elements use the element
27+
* as the source, not the root object.
28+
*/
29+
@AllArgsConstructor
30+
@Getter
31+
@ToString
32+
public class FromFooContainingList {
33+
private final String id;
34+
private final List<FromFooWithFullName> items;
35+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright (C) 2019-2026 Expedia, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.expediagroup.beans.sample;
17+
18+
import lombok.AllArgsConstructor;
19+
import lombok.Getter;
20+
import lombok.ToString;
21+
22+
/**
23+
* Source bean with a full name field, used to test flat field name transformation
24+
* combined with field mapping across collection elements.
25+
*/
26+
@AllArgsConstructor
27+
@Getter
28+
@ToString
29+
public class FromFooWithFullName {
30+
private final String fullName;
31+
}

0 commit comments

Comments
 (0)