Skip to content

Commit ffd9472

Browse files
committed
BeanUtil#visit... will no process passed collections and maps.
1 parent 5d41377 commit ffd9472

File tree

11 files changed

+232
-21
lines changed

11 files changed

+232
-21
lines changed

CHANGES.xml

+3
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@
327327
<action dev="essiembre" type="update">
328328
BeanUtil#diff now display hashCode for any non-null values.
329329
</action>
330+
<action dev="essiembre" type="update">
331+
BeanUtil#visit... will no process passed collections and maps.
332+
</action>
330333
<action dev="essiembre" type="update">
331334
EnumConverter will now match values regardless of non-alphanumeric
332335
characters they may contain.

src/main/java/com/norconex/commons/lang/bean/BeanUtil.java

+25-13
Original file line numberDiff line numberDiff line change
@@ -988,13 +988,19 @@ private static <T> boolean doVisit(VisitArgs<Predicate<T>, T> args) {
988988
&& !args.visitor.test((T) args.bean)) {
989989
return false;
990990
}
991-
for (Object child : getChildren(args.bean)) {
992-
var childArgs = args.withBean(child);
993-
if (!childArgs.visitIfMap(BeanUtil::doVisitMap)
994-
|| !childArgs.visitIfCollection(BeanUtil::doVisitCollection)
995-
|| !doVisit(childArgs)) {
996-
return false;
991+
992+
if (args.visitIfMap(BeanUtil::doVisitMap)
993+
&& args.visitIfCollection(BeanUtil::doVisitCollection)) {
994+
for (Object child : getChildren(args.bean)) {
995+
var childArgs = args.withBean(child);
996+
if (!childArgs.visitIfMap(BeanUtil::doVisitMap)
997+
|| !childArgs.visitIfCollection(BeanUtil::doVisitCollection)
998+
|| !doVisit(childArgs)) {
999+
return false;
1000+
}
9971001
}
1002+
} else {
1003+
return false;
9981004
}
9991005
return true;
10001006
}
@@ -1036,14 +1042,20 @@ private static <T> boolean doVisitProperties(
10361042
}
10371043
}
10381044

1039-
for (Object child : getChildren(args.bean)) {
1040-
var childArgs = args.withBean(child);
1041-
if (!childArgs.visitIfMap(BeanUtil::doVisitPropertiesMap)
1042-
|| !childArgs.visitIfCollection(
1043-
BeanUtil::doVisitPropertiesCollection)
1044-
|| !doVisitProperties(childArgs)) {
1045-
return false;
1045+
if (args.visitIfMap(BeanUtil::doVisitPropertiesMap)
1046+
&& args.visitIfCollection(
1047+
BeanUtil::doVisitPropertiesCollection)) {
1048+
for (Object child : getChildren(args.bean)) {
1049+
var childArgs = args.withBean(child);
1050+
if (!childArgs.visitIfMap(BeanUtil::doVisitPropertiesMap)
1051+
|| !childArgs.visitIfCollection(
1052+
BeanUtil::doVisitPropertiesCollection)
1053+
|| !doVisitProperties(childArgs)) {
1054+
return false;
1055+
}
10461056
}
1057+
} else {
1058+
return false;
10471059
}
10481060
return true;
10491061
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* Copyright 2023 Norconex Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package com.norconex.commons.lang.flow;
16+
17+
import java.util.Objects;
18+
19+
/**
20+
* An abstract {@link FlowConsumerAdapter} that takes care setting/getting
21+
* the adaptee and leaves only the {@link #accept(Object)} method to implement.
22+
* @param <A> the adaptee type
23+
* @param <T> the type being processed by this consuemr adapter
24+
*/
25+
public abstract class BaseConsumerAdapter<A, T>
26+
implements FlowConsumerAdapter<T> {
27+
28+
private A adaptee;
29+
30+
@Override
31+
public A getConsumerAdaptee() {
32+
return adaptee;
33+
}
34+
@SuppressWarnings("unchecked")
35+
@Override
36+
public void setConsumerAdaptee(Object adaptee) {
37+
this.adaptee = (A) adaptee;
38+
}
39+
@Override
40+
public String toString() {
41+
return Objects.toString(adaptee, "");
42+
}
43+
@Override
44+
public boolean equals(Object obj) {
45+
return Objects.equals(adaptee, obj);
46+
}
47+
@Override
48+
public int hashCode() {
49+
return Objects.hashCode(adaptee);
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* Copyright 2023 Norconex Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package com.norconex.commons.lang.flow;
16+
17+
import java.util.Objects;
18+
19+
/**
20+
* An abstract {@link FlowPredicateAdapter} that takes care setting/getting
21+
* the adaptee and leaves only the {@link #test(Object)} method to implement.
22+
* @param <A> the adaptee type
23+
* @param <T> the type being evaluated by this predicate adapter
24+
*/
25+
public abstract class BasePredicateAdapter<A, T>
26+
implements FlowPredicateAdapter<T> {
27+
28+
private A adaptee;
29+
30+
@Override
31+
public A getPredicateAdaptee() {
32+
return adaptee;
33+
}
34+
@SuppressWarnings("unchecked")
35+
@Override
36+
public void setPredicateAdaptee(Object adaptee) {
37+
this.adaptee = (A) adaptee;
38+
}
39+
@Override
40+
public String toString() {
41+
return Objects.toString(adaptee, "");
42+
}
43+
@Override
44+
public boolean equals(Object obj) {
45+
return Objects.equals(adaptee, obj);
46+
}
47+
@Override
48+
public int hashCode() {
49+
return Objects.hashCode(adaptee);
50+
}
51+
}

src/main/java/com/norconex/commons/lang/flow/JsonFlow.java

+19
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.lang.annotation.Retention;
2121
import java.lang.annotation.Target;
2222
import java.util.function.Consumer;
23+
import java.util.function.Supplier;
2324

2425
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
2526
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@@ -38,4 +39,22 @@
3839
@JsonSerialize(typing = Typing.STATIC)
3940
public @interface JsonFlow {
4041

42+
/**
43+
* Builder of a flow mapper configuration. Defaults to no builder,
44+
* using the flow mapper configuration defined in the BeanMapepr (if any).
45+
* @return flow mapper configuration builder concrete type
46+
*/
47+
public Class<? extends Supplier<FlowMapperConfig>> builder()
48+
default NoBuilder.class;
49+
50+
/**
51+
* No-op config mapper builder symbolizing no builder.
52+
*/
53+
public static final class NoBuilder
54+
implements Supplier<FlowMapperConfig> {
55+
@Override
56+
public FlowMapperConfig get() {
57+
return null;
58+
}
59+
}
4160
}

src/main/java/com/norconex/commons/lang/flow/module/FlowDeserializer.java

+20-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
*/
1515
package com.norconex.commons.lang.flow.module;
1616

17+
import static java.util.Optional.ofNullable;
18+
1719
import java.io.IOException;
1820
import java.util.function.Consumer;
1921

@@ -24,8 +26,10 @@
2426
import com.fasterxml.jackson.databind.JsonMappingException;
2527
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
2628
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
29+
import com.norconex.commons.lang.ClassUtil;
2730
import com.norconex.commons.lang.flow.FlowMapperConfig;
2831
import com.norconex.commons.lang.flow.JsonFlow;
32+
import com.norconex.commons.lang.flow.JsonFlow.NoBuilder;
2933

3034
import lombok.Data;
3135
import lombok.RequiredArgsConstructor;
@@ -42,6 +46,7 @@ public class FlowDeserializer<T> extends JsonDeserializer<Consumer<T>>
4246

4347
private final FlowMapperConfig config;
4448
private final JsonDeserializer<?> defaultDeserializer;
49+
private final ThreadLocal<FlowMapperConfig> propCfg = new ThreadLocal<>();
4550

4651
@SuppressWarnings("unchecked")
4752
private RootHandler<T> rootHandler =
@@ -50,7 +55,8 @@ public class FlowDeserializer<T> extends JsonDeserializer<Consumer<T>>
5055
@Override
5156
public Consumer<T> deserialize(JsonParser p, DeserializationContext ctx)
5257
throws IOException {
53-
return rootHandler.read(new FlowDeserContext(config, p));
58+
return rootHandler.read(new FlowDeserContext(
59+
ofNullable(propCfg.get()).orElse(config), p));
5460
}
5561

5662
@Override
@@ -60,8 +66,19 @@ public JsonDeserializer<?> createContextual(
6066
if (property == null) {
6167
return defaultDeserializer;
6268
}
63-
return property.getAnnotation(JsonFlow.class) == null
64-
? defaultDeserializer : this;
69+
var flowAnnot = property.getAnnotation(JsonFlow.class);
70+
if (flowAnnot == null) {
71+
return defaultDeserializer;
72+
}
73+
74+
// If needed, create property-specific flow mapper config
75+
var supplier = flowAnnot.builder();
76+
if (supplier.equals(NoBuilder.class)) {
77+
propCfg.remove();
78+
} else {
79+
propCfg.set(ClassUtil.newInstance(supplier).get());
80+
}
81+
return this;
6582
}
6683

6784
@Override

src/main/java/com/norconex/commons/lang/flow/module/FlowSerializer.java

+21-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
*/
1515
package com.norconex.commons.lang.flow.module;
1616

17+
import static java.util.Optional.ofNullable;
18+
1719
import java.io.IOException;
1820
import java.util.function.Consumer;
1921

@@ -24,8 +26,10 @@
2426
import com.fasterxml.jackson.databind.SerializerProvider;
2527
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
2628
import com.fasterxml.jackson.databind.ser.ResolvableSerializer;
29+
import com.norconex.commons.lang.ClassUtil;
2730
import com.norconex.commons.lang.flow.FlowMapperConfig;
2831
import com.norconex.commons.lang.flow.JsonFlow;
32+
import com.norconex.commons.lang.flow.JsonFlow.NoBuilder;
2933

3034
import lombok.Data;
3135
import lombok.RequiredArgsConstructor;
@@ -42,6 +46,7 @@ public class FlowSerializer<T> extends JsonSerializer<Consumer<T>>
4246

4347
private final FlowMapperConfig config;
4448
private final JsonSerializer<?> defaultSerializer;
49+
private final ThreadLocal<FlowMapperConfig> propCfg = new ThreadLocal<>();
4550

4651
@SuppressWarnings("unchecked")
4752
private RootHandler<T> rootHandler =
@@ -52,7 +57,8 @@ public void serialize(
5257
Consumer<T> value,
5358
JsonGenerator gen,
5459
SerializerProvider sp) throws IOException {
55-
rootHandler.write(value, new FlowSerContext(config, gen));
60+
rootHandler.write(value, new FlowSerContext(
61+
ofNullable(propCfg.get()).orElse(config), gen));
5662
}
5763

5864
@Override
@@ -62,8 +68,20 @@ public JsonSerializer<?> createContextual(
6268
if (property == null) {
6369
return defaultSerializer;
6470
}
65-
return property.getAnnotation(JsonFlow.class) == null
66-
? defaultSerializer : this;
71+
72+
var flowAnnot = property.getAnnotation(JsonFlow.class);
73+
if (flowAnnot == null) {
74+
return defaultSerializer;
75+
}
76+
77+
// If needed, create property-specific flow mapper config
78+
var supplier = flowAnnot.builder();
79+
if (supplier.equals(NoBuilder.class)) {
80+
propCfg.remove();
81+
} else {
82+
propCfg.set(ClassUtil.newInstance(supplier).get());
83+
}
84+
return this;
6785
}
6886

6987
@Override

src/main/java/com/norconex/commons/lang/function/Consumers.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public final void accept(T t) {
6969
* @return a consumer group
7070
* @since 3.0.0
7171
*/
72-
@SuppressWarnings("unchecked")
72+
@SafeVarargs
7373
public static <T> Consumers<T> of(@NonNull Consumer<T>... consumers) {
7474
return new Consumers<>(List.of(consumers));
7575
}

src/test/java/com/norconex/commons/lang/bean/BeanUtilTest.java

+26
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,19 @@ void testVisitAllObjectConsumerClass() {
494494
assertThat(names).containsExactly("Sub1Yes", "Sub3Yes", "Sub3_1Yes");
495495
}
496496

497+
@Test
498+
void testVisitAllObjectCollectionConsumerClass() {
499+
List<String> names = new ArrayList<>();
500+
List<Root> roots = List.of(new Root(), new Root());
501+
BeanUtil.visitAll(
502+
roots,
503+
o -> names.add(o.getClass().getSimpleName()),
504+
EventListener.class);
505+
assertThat(names).containsExactly(
506+
"Sub1Yes", "Sub3Yes", "Sub3_1Yes",
507+
"Sub1Yes", "Sub3Yes", "Sub3_1Yes");
508+
}
509+
497510
@Test
498511
void testVisitObjectPredicate() {
499512
List<String> names = new ArrayList<>();
@@ -529,6 +542,19 @@ void testVisitAllPropertiesObjectBiConsumer() {
529542
"sub1yes", "sub2no", "sub3yes", "sub1Yes", "sub3_1Yes");
530543
}
531544

545+
546+
@Test
547+
void testVisitAllPropertiesObjectCollectionBiConsumer() {
548+
List<String> names = new ArrayList<>();
549+
List<Root> roots = List.of(new Root(), new Root());
550+
BeanUtil.visitAllProperties(
551+
roots,
552+
(o, pd) -> names.add(pd.getName()));
553+
assertThat(names).containsExactly(
554+
"sub1yes", "sub2no", "sub3yes", "sub1Yes", "sub3_1Yes",
555+
"sub1yes", "sub2no", "sub3yes", "sub1Yes", "sub3_1Yes");
556+
}
557+
532558
@Test
533559
void testVisitAllPropertiesObjectBiConsumerClass() {
534560
List<String> names = new ArrayList<>();

src/test/java/com/norconex/commons/lang/flow/FlowTest.yaml

+13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
# Copyright 2023 Norconex Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
114
---
215
flowTest:
316
- if:

0 commit comments

Comments
 (0)