Skip to content

Commit 67e88f0

Browse files
Merge pull request #34 from 42BV/25-non-args-constructors
25 non args constructors
2 parents b9315d0 + 498404c commit 67e88f0

27 files changed

+473
-88
lines changed

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

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,29 @@
11
package io.beanmapper;
22

3+
import io.beanmapper.annotations.BeanConstruct;
34
import io.beanmapper.annotations.BeanDefault;
45
import io.beanmapper.annotations.BeanProperty;
56
import io.beanmapper.core.BeanField;
67
import io.beanmapper.core.BeanFieldMatch;
78
import io.beanmapper.core.BeanMatch;
89
import io.beanmapper.core.BeanMatchStore;
910
import io.beanmapper.core.constructor.BeanInitializer;
10-
import io.beanmapper.core.constructor.NoArgConstructorBeanInitializer;
11+
import io.beanmapper.core.constructor.DefaultBeanInitializer;
1112
import io.beanmapper.core.converter.BeanConverter;
1213
import io.beanmapper.core.converter.collections.CollectionListConverter;
1314
import io.beanmapper.core.converter.collections.CollectionMapConverter;
1415
import io.beanmapper.core.converter.collections.CollectionSetConverter;
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;
16+
import io.beanmapper.core.converter.impl.*;
2317
import io.beanmapper.core.rule.BeanMapperRule;
2418
import io.beanmapper.core.rule.ConstantMapperRule;
2519
import io.beanmapper.core.unproxy.BeanUnproxy;
2620
import io.beanmapper.core.unproxy.DefaultBeanUnproxy;
2721
import io.beanmapper.core.unproxy.SkippingBeanUnproxy;
2822
import io.beanmapper.exceptions.BeanConversionException;
2923
import io.beanmapper.exceptions.BeanFieldNoMatchException;
24+
import io.beanmapper.exceptions.BeanInstantiationException;
3025
import io.beanmapper.exceptions.BeanMappingException;
26+
import io.beanmapper.utils.ConstructorArguments;
3127

3228
import java.util.ArrayList;
3329
import java.util.Collection;
@@ -44,7 +40,7 @@ public class BeanMapper {
4440
/**
4541
* Initializes the beans.
4642
*/
47-
private BeanInitializer beanInitializer = new NoArgConstructorBeanInitializer();
43+
private BeanInitializer beanInitializer = new DefaultBeanInitializer();
4844

4945
/**
5046
* Removes any potential proxies of beans.
@@ -136,8 +132,10 @@ public <S, T> T map(S source, Class<T> targetClass, BeanInitializer beanInitiali
136132
}
137133
}
138134

139-
T target = beanInitializer.instantiate(targetClass);
140-
return map(source, target);
135+
BeanMatch beanMatch = getBeanMatch(source.getClass(), targetClass);
136+
T target = beanInitializer.instantiate(targetClass, getConstructorArguments(source, beanMatch));
137+
138+
return processFields(source, target, beanMatch, new ConstantMapperRule(true));
141139
}
142140

143141
/**
@@ -166,7 +164,7 @@ public <S, T> Collection<T> map(Collection<S> sourceItems, Class<T> targetClass)
166164
*/
167165
@SuppressWarnings({ "unchecked", "rawtypes" })
168166
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);
167+
Collection<T> targetItems = (Collection<T>) beanInitializer.instantiate(collectionClass, null);
170168
for (S source : sourceItems) {
171169
targetItems.add(map(source, targetClass));
172170
}
@@ -183,7 +181,8 @@ public <S, T> Collection<T> map(Collection<S> sourceItems, Class<T> targetClass,
183181
* @throws BeanMappingException
184182
*/
185183
public <S, T> T map(S source, T target) {
186-
return map(source, target, new ConstantMapperRule(true));
184+
BeanMatch beanMatch = getBeanMatch(source.getClass(), target.getClass());
185+
return processFields(source, target, beanMatch, new ConstantMapperRule(true));
187186
}
188187

189188
/**
@@ -197,7 +196,44 @@ public <S, T> T map(S source, T target) {
197196
* @throws BeanMappingException
198197
*/
199198
public <S, T> T map(S source, T target, BeanMapperRule rule) {
200-
return matchSourceToTarget(source, target, rule);
199+
BeanMatch beanMatch = getBeanMatch(source.getClass(), target.getClass());
200+
return processFields(source, target, beanMatch, rule);
201+
}
202+
203+
private <S> ConstructorArguments getConstructorArguments(S source, BeanMatch beanMatch) {
204+
BeanConstruct beanConstruct = beanMatch.getTargetClass().getAnnotation(BeanConstruct.class);
205+
206+
if(beanConstruct == null){
207+
beanConstruct = beanMatch.getSourceClass().getAnnotation(BeanConstruct.class);
208+
}
209+
210+
String[] constructArgs;
211+
ConstructorArguments arguments = null;
212+
213+
if(beanConstruct != null){
214+
constructArgs = beanConstruct.value();
215+
arguments = new ConstructorArguments(constructArgs.length);
216+
217+
for(int i=0; i<constructArgs.length; i++) {
218+
if (beanMatch.getSourceNode().containsKey(constructArgs[i]) || beanMatch.getAliases().containsKey(constructArgs[i])) {
219+
BeanField constructField = beanMatch.getSourceNode().get(constructArgs[i]);
220+
if(constructField == null) {
221+
constructField = beanMatch.getAliases().get(constructArgs[i]);
222+
}
223+
arguments.types[i] = constructField.getProperty().getType();
224+
arguments.values[i] = constructField.getObject(source);
225+
} else {
226+
throw new BeanInstantiationException(beanMatch.getTargetClass(), null);
227+
}
228+
}
229+
}
230+
return arguments;
231+
}
232+
233+
private <T, S> BeanMatch getBeanMatch(Class<S> sourceClazz, Class<T> targetClazz) {
234+
Class<?> sourceClass = beanUnproxy.unproxy(sourceClazz);
235+
Class<?> targetClass = beanUnproxy.unproxy(targetClazz);
236+
return beanMatchStore.getBeanMatch(sourceClass, targetClass);
201237
}
202238

203239
/**
@@ -213,24 +249,23 @@ public <S, T> T map(S source, T target, BeanMapperRule rule) {
213249
* @return A filled target object.
214250
* @throws BeanMappingException
215251
*/
216-
private <S, T> T matchSourceToTarget(S source, T target, BeanMapperRule rule) {
217-
BeanMatch beanMatch = getBeanMatch(source, target);
252+
private <S, T> T processFields(S source, T target, BeanMatch beanMatch, BeanMapperRule rule) {
218253
for (String fieldName : beanMatch.getTargetNode().keySet()) {
219254
BeanField sourceField = beanMatch.getSourceNode().get(fieldName);
255+
if(sourceField == null) {
256+
// No source field found -> check for alias
257+
sourceField = beanMatch.getAliases().get(fieldName);
258+
}
220259
BeanField targetField = beanMatch.getTargetNode().get(fieldName);
221-
if (rule.isAllowed(sourceField, targetField)) {
222-
processField(new BeanFieldMatch(source, target, sourceField, targetField, fieldName), rule);
260+
if(targetField == null) {
261+
// No target field found -> check for alias
262+
targetField = beanMatch.getAliases().get(fieldName);
223263
}
264+
processField(new BeanFieldMatch(source, target, sourceField, targetField, fieldName, beanMatch), rule);
224265
}
225266
return target;
226267
}
227268

228-
private <T, S> BeanMatch getBeanMatch(S source, T target) {
229-
Class<?> sourceClass = beanUnproxy.unproxy(source.getClass());
230-
Class<?> targetClass = beanUnproxy.unproxy(target.getClass());
231-
return beanMatchStore.getBeanMatch(sourceClass, targetClass);
232-
}
233-
234269
/**
235270
* Process a single combination of a source and a target field.
236271
* @param beanFieldMatch contains the fields belonging to the source/target field match
@@ -296,9 +331,14 @@ private void copySourceToTarget(BeanFieldMatch beanFieldMatch) {
296331
*/
297332
private void dealWithMappableNestedClass(BeanFieldMatch beanFieldMatch, BeanMapperRule rule) {
298333
Object encapsulatedSource = beanFieldMatch.getSourceObject();
334+
Object target;
299335
if (encapsulatedSource != null) {
300-
Object encapsulatedTarget = beanFieldMatch.getOrCreateTargetObject();
301-
matchSourceToTarget(encapsulatedSource, encapsulatedTarget, rule);
336+
if(beanFieldMatch.getTargetObject() == null){
337+
target = map(encapsulatedSource, beanFieldMatch.getTargetClass());
338+
}else {
339+
target = map(encapsulatedSource, beanFieldMatch.getTarget(), rule);
340+
}
341+
beanFieldMatch.writeObject(target);
302342
}
303343
}
304344

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.beanmapper.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Setting a value makes the property available for constructor or other fields to map.
10+
*/
11+
@Target({ ElementType.FIELD, ElementType.METHOD })
12+
@Retention(RetentionPolicy.RUNTIME)
13+
public @interface BeanAlias {
14+
15+
String value();
16+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.beanmapper.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* This annotation is used to pass source fields into a overloaded constructor.
10+
*/
11+
@Target({ ElementType.FIELD, ElementType.TYPE })
12+
@Retention(RetentionPolicy.RUNTIME)
13+
public @interface BeanConstruct {
14+
15+
String[] value();
16+
17+
}

src/main/java/io/beanmapper/core/BeanField.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package io.beanmapper.core;
22

3-
import io.beanmapper.core.constructor.NoArgConstructorBeanInitializer;
3+
import io.beanmapper.annotations.BeanConstruct;
4+
import io.beanmapper.core.constructor.DefaultBeanInitializer;
45
import io.beanmapper.core.converter.collections.BeanCollectionInstructions;
56
import io.beanmapper.core.inspector.PropertyAccessor;
67
import io.beanmapper.core.inspector.PropertyAccessors;
8+
import io.beanmapper.exceptions.BeanInstantiationException;
79
import io.beanmapper.exceptions.BeanMappingException;
810
import io.beanmapper.exceptions.NoSuchPropertyException;
11+
import io.beanmapper.utils.ConstructorArguments;
912
import io.beanmapper.utils.DefaultValues;
1013

1114
import java.util.Stack;
@@ -74,30 +77,53 @@ public Object getObject(Object object) throws BeanMappingException {
7477
return object;
7578
}
7679

77-
public Object getOrCreate(Object parent) throws BeanMappingException {
80+
public Object getOrCreate(Object parent, Object source, BeanMatch beanMatch) throws BeanMappingException {
7881
Object target = getCurrentField().getValue(parent);
7982
if (target == null) {
8083
Class<?> type = getCurrentField().getType();
81-
target = new NoArgConstructorBeanInitializer().instantiate(type);
84+
BeanConstruct beanConstruct = type.getAnnotation(BeanConstruct.class);
85+
86+
if(beanConstruct == null){
87+
target = new DefaultBeanInitializer().instantiate(type, null);
88+
}else {
89+
String[] constructArgs = beanConstruct.value();
90+
ConstructorArguments arguments = new ConstructorArguments(constructArgs.length);;
91+
92+
for(int i=0; i<constructArgs.length; i++) {
93+
if (beanMatch.getSourceNode().containsKey(constructArgs[i]) || beanMatch.getAliases().containsKey(constructArgs[i])) {
94+
BeanField constructField = beanMatch.getSourceNode().get(constructArgs[i]);
95+
if(constructField == null) {
96+
constructField = beanMatch.getAliases().get(constructArgs[i]);
97+
}
98+
arguments.types[i] = constructField.getProperty().getType();
99+
arguments.values[i] = constructField.getObject(source);
100+
} else {
101+
throw new BeanInstantiationException(beanMatch.getTargetClass(), null);
102+
}
103+
}
104+
105+
target = new DefaultBeanInitializer().instantiate(type, arguments);
106+
}
107+
82108
getCurrentField().setValue(parent, target);
83109
}
84110
return target;
85111
}
86112

87-
public Object writeObject(Object source, Object parent) throws BeanMappingException {
113+
public Object writeObject(Object value, Object parent, Object source, BeanMatch beanMatch) throws BeanMappingException {
88114
if (hasNext()) {
89-
if(source != null) {
90-
getNext().writeObject(source, getOrCreate(parent));
115+
if(value != null) {
116+
getNext().writeObject(value, getOrCreate(parent, source, beanMatch), source, beanMatch);
91117
} else if (getCurrentField().getValue(parent) != null) {
92118
// If source is null and target object is filled. The nested target object should be overridden with null.
93-
getNext().writeObject(null, getCurrentField().getValue(parent));
119+
getNext().writeObject(null, getCurrentField().getValue(parent), source, beanMatch);
94120
}
95121
} else {
96-
if(source == null && getCurrentField().getType().isPrimitive()) {
122+
if(value == null && getCurrentField().getType().isPrimitive()) {
97123
// Primitives types can't be null.
98-
source = DefaultValues.defaultValueFor(getCurrentField().getType());
124+
value = DefaultValues.defaultValueFor(getCurrentField().getType());
99125
}
100-
getCurrentField().setValue(parent, source);
126+
getCurrentField().setValue(parent, value);
101127
}
102128
return parent;
103129
}

src/main/java/io/beanmapper/core/BeanFieldMatch.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@
77
import java.lang.annotation.Annotation;
88

99
public class BeanFieldMatch {
10-
10+
11+
private BeanMatch beanMatch;
1112
private Object source;
1213
private Object target;
1314
private BeanField sourceBeanField;
1415
private BeanField targetBeanField;
1516
private String targetFieldName;
1617

17-
public BeanFieldMatch(Object source, Object target, BeanField sourceBeanField, BeanField targetBeanField, String targetFieldName) {
18+
public BeanFieldMatch(Object source, Object target, BeanField sourceBeanField, BeanField targetBeanField, String targetFieldName, BeanMatch beanMatch) {
1819
this.source = source;
1920
this.target = target;
2021
this.sourceBeanField = sourceBeanField;
2122
this.targetBeanField = targetBeanField;
2223
this.targetFieldName = targetFieldName;
24+
this.beanMatch = beanMatch;
2325
}
2426
public boolean hasSimilarClasses() {
2527
return sourceBeanField.getProperty().getType().equals(targetBeanField.getProperty().getType());
@@ -56,22 +58,23 @@ public void setTarget(Object value) throws BeanMappingException {
5658
targetBeanField.getProperty().setValue(target, value);
5759
}
5860
public void writeObject(Object value) throws BeanMappingException {
59-
targetBeanField.writeObject(value, target);
61+
targetBeanField.writeObject(value, target, source, beanMatch);
6062
}
6163
public Object getSourceObject() throws BeanMappingException {
6264
return sourceBeanField.getObject(source);
6365
}
6466
public Object getTargetObject() throws BeanMappingException {
6567
return targetBeanField.getObject(target);
6668
}
67-
public Object getOrCreateTargetObject() throws BeanMappingException {
68-
return targetBeanField.getOrCreate(target);
69-
}
7069
public BeanCollectionInstructions getCollectionInstructions() {
7170
return targetBeanField.getCollectionInstructions() != null ?
7271
targetBeanField.getCollectionInstructions() :
7372
sourceBeanField.getCollectionInstructions() != null ?
7473
sourceBeanField.getCollectionInstructions() :
7574
null;
7675
}
76+
77+
public BeanMatch getBeanMatch() {
78+
return beanMatch;
79+
}
7780
}

src/main/java/io/beanmapper/core/BeanMatch.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ public class BeanMatch {
1212

1313
private final Map<String, BeanField> targetNode;
1414

15-
public BeanMatch(Class<?> sourceClass, Class<?> targetClass, Map<String, BeanField> sourceNode, Map<String, BeanField> targetNode) {
15+
private final Map<String, BeanField> aliases;
16+
17+
public BeanMatch(Class<?> sourceClass, Class<?> targetClass, Map<String, BeanField> sourceNode, Map<String, BeanField> targetNode, Map<String, BeanField> aliases) {
1618
this.sourceClass = sourceClass;
1719
this.targetClass = targetClass;
1820
this.sourceNode = sourceNode;
1921
this.targetNode = targetNode;
22+
this.aliases = aliases;
2023
}
2124

2225
public Class<?> getSourceClass() {
@@ -35,4 +38,7 @@ public Map<String, BeanField> getTargetNode() {
3538
return targetNode;
3639
}
3740

41+
public Map<String, BeanField> getAliases() {
42+
return aliases;
43+
}
3844
}

0 commit comments

Comments
 (0)