Skip to content

Commit b9315d0

Browse files
author
Jeroen van Schagen
committed
added a rule interface in which we can be selective in what to map and what to skip. this is needed for my patch release in restify
1 parent d8f3dc6 commit b9315d0

File tree

6 files changed

+215
-25
lines changed

6 files changed

+215
-25
lines changed

src/main/java/io/beanmapper/BeanMapper.java

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,16 @@
1212
import io.beanmapper.core.converter.collections.CollectionListConverter;
1313
import io.beanmapper.core.converter.collections.CollectionMapConverter;
1414
import io.beanmapper.core.converter.collections.CollectionSetConverter;
15-
import io.beanmapper.core.converter.impl.*;
15+
import io.beanmapper.core.converter.impl.NumberToNumberConverter;
16+
import io.beanmapper.core.converter.impl.ObjectToStringConverter;
17+
import io.beanmapper.core.converter.impl.PrimitiveConverter;
18+
import io.beanmapper.core.converter.impl.StringToBigDecimalConverter;
19+
import io.beanmapper.core.converter.impl.StringToBooleanConverter;
20+
import io.beanmapper.core.converter.impl.StringToEnumConverter;
21+
import io.beanmapper.core.converter.impl.StringToIntegerConverter;
22+
import io.beanmapper.core.converter.impl.StringToLongConverter;
23+
import io.beanmapper.core.rule.BeanMapperRule;
24+
import io.beanmapper.core.rule.ConstantMapperRule;
1625
import io.beanmapper.core.unproxy.BeanUnproxy;
1726
import io.beanmapper.core.unproxy.DefaultBeanUnproxy;
1827
import io.beanmapper.core.unproxy.SkippingBeanUnproxy;
@@ -91,11 +100,27 @@ public <S, T> T map(S source, Class<T> targetClass) {
91100
return map(source, targetClass, beanInitializer, false);
92101
}
93102

103+
/**
104+
* Copies the values from the source object to a newly constructed target instance
105+
* @param source source instance of the properties
106+
* @param targetClass class of the target, needs to be constructed as the target instance
107+
* @param converterChoosable when {@code true} a plain conversion is checked before mapping
108+
* @param <S> The instance from which the properties get copied
109+
* @param <T> the instance to which the properties get copied
110+
* @return the target instance containing all applicable properties
111+
* @throws BeanMappingException
112+
*/
113+
@SuppressWarnings("unchecked")
114+
public <S, T> T map(S source, Class<T> targetClass, boolean converterChoosable) {
115+
return map(source, targetClass, beanInitializer, converterChoosable);
116+
}
117+
94118
/**
95119
* Copies the values from the source object to a newly constructed target instance
96120
* @param source source instance of the properties
97121
* @param targetClass class of the target, needs to be constructed as the target instance
98122
* @param beanInitializer initializes the beans
123+
* @param converterChoosable when {@code true} a plain conversion is checked before mapping
99124
* @param <S> The instance from which the properties get copied
100125
* @param <T> the instance to which the properties get copied
101126
* @return the target instance containing all applicable properties
@@ -115,22 +140,33 @@ public <S, T> T map(S source, Class<T> targetClass, BeanInitializer beanInitiali
115140
return map(source, target);
116141
}
117142

118-
public <S, T> T mapForListElement(S source, Class<T> targetClass) {
119-
return map(source, targetClass, beanInitializer, true);
120-
}
121-
122143
/**
123144
* Maps a list of source items to a list of target items with a specific class
124145
* @param sourceItems the items to be mapped
125146
* @param targetClass the class type of the items in the returned list
126-
* @param <S> source
127-
* @param <T> target
147+
* @param <S> the instance from which the properties get copied.
148+
* @param <T> the instance to which the properties get copied
128149
* @return the list of mapped items with class T
129150
* @throws BeanMappingException
130151
*/
131152
@SuppressWarnings("unchecked")
132153
public <S, T> Collection<T> map(Collection<S> sourceItems, Class<T> targetClass) {
133-
Collection<T> targetItems = (Collection<T>) beanInitializer.instantiate(sourceItems.getClass());
154+
return map(sourceItems, targetClass, sourceItems.getClass());
155+
}
156+
157+
/**
158+
* Maps a list of source items to a list of target items with a specific class
159+
* @param sourceItems the items to be mapped
160+
* @param targetClass the class type of the items in the returned list
161+
* @param collectionClass the collection class
162+
* @param <S> the instance from which the properties get copied.
163+
* @param <T> the instance to which the properties get copied
164+
* @return the list of mapped items with class T
165+
* @throws BeanMappingException
166+
*/
167+
@SuppressWarnings({ "unchecked", "rawtypes" })
168+
public <S, T> Collection<T> map(Collection<S> sourceItems, Class<T> targetClass, Class<? extends Collection> collectionClass) {
169+
Collection<T> targetItems = (Collection<T>) beanInitializer.instantiate(collectionClass);
134170
for (S source : sourceItems) {
135171
targetItems.add(map(source, targetClass));
136172
}
@@ -141,13 +177,27 @@ public <S, T> Collection<T> map(Collection<S> sourceItems, Class<T> targetClass)
141177
* Copies the values from the source object to an existing target instance
142178
* @param source source instance of the properties
143179
* @param target target instance for the properties
144-
* @param <S> The instance from which the properties get copied.
180+
* @param <S> the instance from which the properties get copied.
145181
* @param <T> the instance to which the properties get copied
146182
* @return the original target instance containing all applicable properties
147183
* @throws BeanMappingException
148184
*/
149185
public <S, T> T map(S source, T target) {
150-
return matchSourceToTarget(source, target);
186+
return map(source, target, new ConstantMapperRule(true));
187+
}
188+
189+
/**
190+
* Copies the values from the source object to an existing target instance
191+
* @param source source instance of the properties
192+
* @param target target instance for the properties
193+
* @param rule the matching rule
194+
* @param <S> the instance from which the properties get copied.
195+
* @param <T> the instance to which the properties get copied
196+
* @return the original target instance containing all applicable properties
197+
* @throws BeanMappingException
198+
*/
199+
public <S, T> T map(S source, T target, BeanMapperRule rule) {
200+
return matchSourceToTarget(source, target, rule);
151201
}
152202

153203
/**
@@ -163,12 +213,14 @@ public <S, T> T map(S source, T target) {
163213
* @return A filled target object.
164214
* @throws BeanMappingException
165215
*/
166-
private <S, T> T matchSourceToTarget(S source, T target) {
216+
private <S, T> T matchSourceToTarget(S source, T target, BeanMapperRule rule) {
167217
BeanMatch beanMatch = getBeanMatch(source, target);
168218
for (String fieldName : beanMatch.getTargetNode().keySet()) {
169219
BeanField sourceField = beanMatch.getSourceNode().get(fieldName);
170220
BeanField targetField = beanMatch.getTargetNode().get(fieldName);
171-
processField(new BeanFieldMatch(source, target, sourceField, targetField, fieldName));
221+
if (rule.isAllowed(sourceField, targetField)) {
222+
processField(new BeanFieldMatch(source, target, sourceField, targetField, fieldName), rule);
223+
}
172224
}
173225
return target;
174226
}
@@ -184,7 +236,7 @@ private <T, S> BeanMatch getBeanMatch(S source, T target) {
184236
* @param beanFieldMatch contains the fields belonging to the source/target field match
185237
* @throws BeanMappingException
186238
*/
187-
private void processField(BeanFieldMatch beanFieldMatch) {
239+
private void processField(BeanFieldMatch beanFieldMatch, BeanMapperRule rule) {
188240
if (!beanFieldMatch.hasMatchingSource()) {
189241
dealWithNonMatchingNode(beanFieldMatch);
190242
return;
@@ -194,7 +246,7 @@ private void processField(BeanFieldMatch beanFieldMatch) {
194246
isMappableClass(beanFieldMatch.getTargetClass()) &&
195247
beanFieldMatch.getSourceObject() != null) {
196248

197-
dealWithMappableNestedClass(beanFieldMatch);
249+
dealWithMappableNestedClass(beanFieldMatch, rule);
198250
return;
199251
}
200252
if (beanFieldMatch.isMappable()) {
@@ -239,13 +291,14 @@ private void copySourceToTarget(BeanFieldMatch beanFieldMatch) {
239291
* If the field is a class which can itself be mapped to another class, it must be treated
240292
* as such. The matching process is called recursively to deal with this pair.
241293
* @param beanFieldMatch contains the fields belonging to the source/target field match
294+
* @param rule the matching rule
242295
* @throws BeanMappingException
243296
*/
244-
private void dealWithMappableNestedClass(BeanFieldMatch beanFieldMatch) {
297+
private void dealWithMappableNestedClass(BeanFieldMatch beanFieldMatch, BeanMapperRule rule) {
245298
Object encapsulatedSource = beanFieldMatch.getSourceObject();
246299
if (encapsulatedSource != null) {
247300
Object encapsulatedTarget = beanFieldMatch.getOrCreateTargetObject();
248-
matchSourceToTarget(encapsulatedSource, encapsulatedTarget);
301+
matchSourceToTarget(encapsulatedSource, encapsulatedTarget, rule);
249302
}
250303
}
251304

src/main/java/io/beanmapper/core/converter/collections/AbstractCollectionConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ private T getTargetCollection(BeanFieldMatch beanFieldMatch) {
6363
}
6464

6565
private Object convertElement(Object source, BeanFieldMatch beanFieldMatch) {
66-
return beanMapper.mapForListElement(source, beanFieldMatch.getCollectionInstructions().getCollectionMapsTo());
66+
return beanMapper.map(source, beanFieldMatch.getCollectionInstructions().getCollectionMapsTo(), true);
6767
}
6868

6969
@Override
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* (C) 2014 42 bv (www.42.nl). All rights reserved.
3+
*/
4+
package io.beanmapper.core.rule;
5+
6+
import io.beanmapper.core.BeanField;
7+
8+
/**
9+
* Determines if a field should be mapped or not.
10+
*
11+
* @author Jeroen van Schagen
12+
* @since Nov 6, 2015
13+
*/
14+
public interface BeanMapperRule {
15+
16+
/**
17+
* Determines if a certain field match should be performed.
18+
*
19+
* @param sourceField the source field
20+
* @param targetField the target field
21+
* @return {@code true} if the map should be performed, else {@code false}
22+
*/
23+
boolean isAllowed(BeanField sourceField, BeanField targetField);
24+
25+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* (C) 2014 42 bv (www.42.nl). All rights reserved.
3+
*/
4+
package io.beanmapper.core.rule;
5+
6+
import io.beanmapper.core.BeanField;
7+
8+
/**
9+
* Allows each property to be mapped.
10+
*
11+
* @author Jeroen van Schagen
12+
* @since Nov 6, 2015
13+
*/
14+
public class ConstantMapperRule implements BeanMapperRule {
15+
16+
private final boolean allowed;
17+
18+
public ConstantMapperRule(boolean allowed) {
19+
this.allowed = allowed;
20+
}
21+
22+
/**
23+
* {@inheritDoc}
24+
*/
25+
@Override
26+
public boolean isAllowed(BeanField sourceField, BeanField targetField) {
27+
return allowed;
28+
}
29+
30+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* (C) 2014 42 bv (www.42.nl). All rights reserved.
3+
*/
4+
package io.beanmapper.core.rule;
5+
6+
import io.beanmapper.core.BeanField;
7+
8+
import java.util.Arrays;
9+
import java.util.Collection;
10+
import java.util.HashSet;
11+
import java.util.Set;
12+
13+
/**
14+
* Maps each property when the source field is included.
15+
*
16+
* @author Jeroen van Schagen
17+
* @since Nov 6, 2015
18+
*/
19+
public class SourceFieldMapperRule implements BeanMapperRule {
20+
21+
private final Set<String> fieldNames;
22+
23+
public SourceFieldMapperRule(Collection<String> fieldNames) {
24+
this.fieldNames = new HashSet<String>(fieldNames);
25+
}
26+
27+
public SourceFieldMapperRule(String... fieldNames) {
28+
this(Arrays.asList(fieldNames));
29+
}
30+
31+
/**
32+
* {@inheritDoc}
33+
*/
34+
@Override
35+
public boolean isAllowed(BeanField sourceField, BeanField targetField) {
36+
boolean allowed = false;
37+
if (sourceField != null) {
38+
String fieldName = sourceField.getName();
39+
allowed = fieldNames.contains(fieldName);
40+
}
41+
return allowed;
42+
}
43+
44+
}

src/test/java/io/beanmapper/BeanMapperTest.java

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
package io.beanmapper;
22

3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNull;
5+
import static org.junit.Assert.assertTrue;
36
import io.beanmapper.core.converter.impl.LocalDateTimeToLocalDate;
47
import io.beanmapper.core.converter.impl.LocalDateToLocalDateTime;
58
import io.beanmapper.core.converter.impl.NestedSourceClassToNestedTargetClassConverter;
69
import io.beanmapper.core.converter.impl.ObjectToStringConverter;
10+
import io.beanmapper.core.rule.SourceFieldMapperRule;
711
import io.beanmapper.exceptions.BeanMappingException;
8-
import io.beanmapper.testmodel.collections.*;
12+
import io.beanmapper.testmodel.collections.CollectionListSource;
13+
import io.beanmapper.testmodel.collections.CollectionListTarget;
14+
import io.beanmapper.testmodel.collections.CollectionListTargetClear;
15+
import io.beanmapper.testmodel.collections.CollectionMapSource;
16+
import io.beanmapper.testmodel.collections.CollectionMapTarget;
17+
import io.beanmapper.testmodel.collections.CollectionSetSource;
18+
import io.beanmapper.testmodel.collections.CollectionSetTarget;
19+
import io.beanmapper.testmodel.collections.SourceWithListGetter;
20+
import io.beanmapper.testmodel.collections.TargetWithListPublicField;
921
import io.beanmapper.testmodel.converter.SourceWithDate;
1022
import io.beanmapper.testmodel.converter.TargetWithDateTime;
1123
import io.beanmapper.testmodel.converterbetweennestedclasses.NestedSourceClass;
@@ -16,8 +28,14 @@
1628
import io.beanmapper.testmodel.defaults.TargetWithDefaults;
1729
import io.beanmapper.testmodel.emptyobject.EmptySource;
1830
import io.beanmapper.testmodel.emptyobject.EmptyTarget;
31+
import io.beanmapper.testmodel.emptyobject.NestedEmptySource;
1932
import io.beanmapper.testmodel.emptyobject.NestedEmptyTarget;
20-
import io.beanmapper.testmodel.encapsulate.*;
33+
import io.beanmapper.testmodel.encapsulate.Address;
34+
import io.beanmapper.testmodel.encapsulate.Country;
35+
import io.beanmapper.testmodel.encapsulate.House;
36+
import io.beanmapper.testmodel.encapsulate.ResultManyToMany;
37+
import io.beanmapper.testmodel.encapsulate.ResultManyToOne;
38+
import io.beanmapper.testmodel.encapsulate.ResultOneToMany;
2139
import io.beanmapper.testmodel.encapsulate.sourceAnnotated.Car;
2240
import io.beanmapper.testmodel.encapsulate.sourceAnnotated.CarDriver;
2341
import io.beanmapper.testmodel.encapsulate.sourceAnnotated.Driver;
@@ -57,11 +75,6 @@
5775
import io.beanmapper.testmodel.similarsubclasses.SimilarSubclass;
5876
import io.beanmapper.testmodel.tostring.SourceWithNonString;
5977
import io.beanmapper.testmodel.tostring.TargetWithString;
60-
import mockit.Expectations;
61-
import mockit.Mocked;
62-
import mockit.Verifications;
63-
import org.junit.Before;
64-
import org.junit.Test;
6578

6679
import java.time.LocalDate;
6780
import java.time.LocalDateTime;
@@ -70,7 +83,12 @@
7083
import java.util.List;
7184
import java.util.TreeSet;
7285

73-
import static org.junit.Assert.*;
86+
import mockit.Expectations;
87+
import mockit.Mocked;
88+
import mockit.Verifications;
89+
90+
import org.junit.Before;
91+
import org.junit.Test;
7492

7593
public class BeanMapperTest {
7694

@@ -269,6 +287,26 @@ public void emptySourceToExistingTarget() {
269287
assertNull(mappedTarget.nestedEmptyClass.name);
270288
assertNull(mappedTarget.nestedEmpty);
271289
}
290+
291+
@Test
292+
public void sourceToTargetPatch() {
293+
EmptySource source = new EmptySource();
294+
source.name = "My new name";
295+
source.bool = false;
296+
source.nestedEmpty = new NestedEmptySource();
297+
source.nestedEmpty.name = "My new nested name";
298+
299+
EmptyTarget target = new EmptyTarget();
300+
target.name = "My name";
301+
target.bool = true;
302+
target.nestedEmpty = new NestedEmptyTarget();
303+
target.nestedEmpty.name = "My nested name";
304+
305+
beanMapper.map(source, target, new SourceFieldMapperRule("name", "nestedEmpty", "nestedEmpty.name"));
306+
assertEquals("My new name", target.name); // Overwritten
307+
assertEquals(true, target.bool); // Not overwritten because not in rule
308+
assertEquals("My new nested name", target.nestedEmpty.name); // Overwritten (nested)
309+
}
272310

273311
@Test
274312
public void copyToExistingTargetInstance() {

0 commit comments

Comments
 (0)