Kotlin extension with data class support by kendarorg · Pull Request #781 · modelmapper/modelmapper · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions core/src/main/java/org/modelmapper/ConstructorParam.java
15 changes: 15 additions & 0 deletions core/src/main/java/org/modelmapper/config/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.modelmapper.config;

import org.modelmapper.Condition;
import org.modelmapper.spi.ConstructorInjector;
import org.modelmapper.PropertyMap;
import org.modelmapper.Provider;
import org.modelmapper.spi.*;
Expand Down Expand Up @@ -193,6 +194,13 @@ public enum AccessLevel {
*/
List<ValueWriter<?>> getValueWriters();

/**
* Gets the constructor injector.
*
* @see #setConstructorInjector(ConstructorInjector)
*/
ConstructorInjector getConstructorInjector();

/**
* Returns {@code true} if ambiguous properties are ignored or {@code false} if they will result
* in an exception.
Expand Down Expand Up @@ -482,4 +490,11 @@ public enum AccessLevel {
* @throws IllegalArgumentException if {@code namingConvention} is null
*/
Configuration setSourceNamingConvention(NamingConvention namingConvention);

/**
* Allows adding a new constructor injector
*
* @throws IllegalArgumentException if {@code constructorInjector} is null
*/
Configuration setConstructorInjector(ConstructorInjector constructorInjector);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.modelmapper.convention;

import org.modelmapper.ConstructorParam;
import org.modelmapper.spi.ConstructorInjector;

import java.util.ArrayList;
import java.util.List;

public class ConstructorInjectors {
public static final ConstructorInjector NO_CONSTRUCTOR_INJECTORS = new ConstructorInjector() {
@Override
public List<ConstructorParam> getParameters(Class<?> destinationType) {
return new ArrayList<>();
}

@Override
public boolean isApplicable(Class<?> destinationType) {
return false;
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
import java.util.List;

import org.modelmapper.Condition;
import org.modelmapper.spi.ConstructorInjector;
import org.modelmapper.Provider;
import org.modelmapper.config.Configuration;
import org.modelmapper.convention.ConstructorInjectors;
import org.modelmapper.convention.MatchingStrategies;
import org.modelmapper.convention.NameTokenizers;
import org.modelmapper.convention.NameTransformers;
Expand All @@ -45,6 +47,7 @@
* @author Jonathan Halterman
*/
public class InheritingConfiguration implements Configuration {
private ConstructorInjector constructorInjector;
private final Configuration parent;
public final TypeMapStore typeMapStore;
public final ConverterStore converterStore;
Expand Down Expand Up @@ -96,6 +99,7 @@ public InheritingConfiguration() {
skipNullEnabled = Boolean.FALSE;
useOSGiClassLoaderBridging = Boolean.FALSE;
collectionsMergeEnabled = Boolean.FALSE;
constructorInjector = ConstructorInjectors.NO_CONSTRUCTOR_INJECTORS;
}

/**
Expand Down Expand Up @@ -130,6 +134,7 @@ public InheritingConfiguration() {
preferNestedProperties = source.preferNestedProperties;
skipNullEnabled = source.skipNullEnabled;
collectionsMergeEnabled = source.collectionsMergeEnabled;
constructorInjector = source.constructorInjector;
}
}

Expand Down Expand Up @@ -265,6 +270,13 @@ public NamingConvention getSourceNamingConvention() {
: sourceNamingConvention;
}

@Override
public ConstructorInjector getConstructorInjector() {
return constructorInjector == null
? Assert.notNull(parent).getConstructorInjector()
: constructorInjector;
}

@Override
public List<ValueReader<?>> getValueReaders() {
return valueAccessStore.getValueReaders();
Expand All @@ -288,6 +300,7 @@ public int hashCode() {
result = prime * result + getFieldAccessLevel().hashCode();
result = prime * result + getMethodAccessLevel().hashCode();
result = prime * result + getSourceNamingConvention().hashCode();
result = prime * result + getConstructorInjector().hashCode();
result = prime * result + getDestinationNamingConvention().hashCode();
result = prime * result + (isFieldMatchingEnabled() ? 1231 : 1237);
return result;
Expand Down Expand Up @@ -473,6 +486,12 @@ public Configuration setSourceNamingConvention(NamingConvention namingConvention
return this;
}

@Override
public Configuration setConstructorInjector(ConstructorInjector constructorInjector) {
this.constructorInjector = constructorInjector;
return this;
}

@Override
public Configuration setUseOSGiClassLoaderBridging(boolean useOSGiClassLoaderBridging) {
this.useOSGiClassLoaderBridging = useOSGiClassLoaderBridging;
Expand Down
98 changes: 88 additions & 10 deletions core/src/main/java/org/modelmapper/internal/MappingEngineImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -42,7 +43,7 @@
/**
* MappingEngine implementation that caches ConditionalConverters by source and destination type
* pairs.
*
*
* @author Jonathan Halterman
*/
public class MappingEngineImpl implements MappingEngine {
Expand Down Expand Up @@ -136,20 +137,43 @@ <S, D> D typeMap(MappingContextImpl<S, D> context, TypeMap<S, D> typeMap) {

if (noSkip && typeMap.getConverter() != null)
return convert(context, typeMap.getConverter());

if (context.getDestination() == null && Types.isInstantiable(context.getDestinationType())) {
D destination = createDestination(context);
if (destination == null)
return null;
if (!configuration.getConstructorInjector().isApplicable(typeMap.getDestinationType())) {
if (context.getDestination() == null && Types.isInstantiable(context.getDestinationType())) {
D destination = createDestination(context);
if (destination == null)
return null;
}
}

if (noSkip) {
Converter<S, D> converter = typeMap.getPreConverter();
if (converter != null)
context.setDestination(convert(context, converter), true);
if (!configuration.getConstructorInjector().isApplicable(typeMap.getDestinationType())) {
if (converter != null)
context.setDestination(convert(context, converter), true);

for (Mapping mapping : typeMap.getMappings())
propertyMap(mapping, context);
} else {
List<Mapping> constructorMappings = new ArrayList<Mapping>();
List<Object> data = new ArrayList<>();
for (Mapping mapping : typeMap.getMappings()) {
if (mapping.isConstructor()) {
constructorMappings.add(mapping);
data.add(constructorMapResolve(mapping, context));
}
}
D destination = createDestination(context, constructorMappings, data);
for (Mapping mapping : typeMap.getMappings()) {
if (!mapping.isConstructor()) {
propertyMap(mapping, context);
}
}

for (Mapping mapping : typeMap.getMappings())
propertyMap(mapping, context);
if (converter != null)
context.setDestination(convert(context, converter), true);
if (destination == null)
return null;
}

converter = typeMap.getPostConverter();
if (converter != null)
Expand Down Expand Up @@ -380,4 +404,58 @@ void validateDestination(Class<?> destinationType, Object destination, Errors er
if (destination != null && !destinationType.isAssignableFrom(destination.getClass()))
errors.invalidProvidedDestinationInstance(destination, destinationType);
}

private <D, S> D createDestination(MappingContext<S, D> context, List<Mapping> constructorMappings, List<Object> values) {
MappingContextImpl<S, D> contextImpl = (MappingContextImpl<S, D>) context;
D destination = contextImpl.createDestinationViaProvider();
if (destination == null) {
destination = instantiateWithMappings(context.getDestinationType(), contextImpl.errors, constructorMappings, values);
}

contextImpl.setDestination(destination, true);
return destination;
}

private <T> T instantiateWithMappings(Class<T> type, Errors errors, List<Mapping> mappings, List<Object> values) {
try {
Constructor<?>[] constructors = type.getConstructors();
Constructor<T> defaultCtor = null;
Constructor<T> otherConstructor = null;
for (Constructor<?> ctor : constructors) {
if (ctor.getParameterTypes().length == 0) {
defaultCtor = (Constructor<T>) ctor;
break;
} else if (ctor.getParameterTypes().length == values.size()) {
otherConstructor = (Constructor<T>) ctor;
}
}

if (otherConstructor != null && defaultCtor == null) {
if (!otherConstructor.isAccessible()) otherConstructor.setAccessible(true);
List<Object> realValues = new ArrayList<>();
for (int i = values.size() - 1; i >= 0; i--) {
realValues.add(values.get(i));
}
return otherConstructor.newInstance(realValues.toArray());
}
if (!defaultCtor.isAccessible()) defaultCtor.setAccessible(true);
return defaultCtor.newInstance();


} catch (Exception e) {
errors.errorInstantiatingDestination(type, e);
return null;
}
}

private <S, D> Object constructorMapResolve(Mapping mapping, MappingContextImpl<S, D> context) {

MappingImpl mappingImpl = (MappingImpl) mapping;
String propertyPath = context.destinationPath + mappingImpl.getPath();
if (context.isShaded(propertyPath)) return null;
if (mapping.getCondition() == null && mapping.isSkipped()) // skip()
return null;

return resolveSourceValue(context, mapping);
}
}
14 changes: 14 additions & 0 deletions core/src/main/java/org/modelmapper/internal/MappingImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ abstract class MappingImpl implements InternalMapping, Comparable<MappingImpl> {
protected Provider<?> provider;
protected Converter<?, ?> converter;

/**
* Check if has a constructor mutator
* @return
*/
@Override
public boolean isConstructor() {
for(PropertyInfo destinationMutator : destinationMutators){
if(destinationMutator.getClass()== PropertyInfoImpl.ConstructorMutator.class ){
return true;
}
}
return false;
}

/**
* Creates an implicit mapping.
*/
Expand Down
26 changes: 26 additions & 0 deletions core/src/main/java/org/modelmapper/internal/PropertyInfoImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,32 @@ public TypeInfo<?> getTypeInfo(InheritingConfiguration configuration) {
}
}

static class ConstructorMutator extends PropertyInfoImpl<Method> implements Mutator, PropertyInfo {
ConstructorMutator(String name, Class<?> initialType) {
super(initialType,null, PropertyType.CONSTRUCTOR, name);
}

@Override
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
return null;
}

@Override
public void setValue(Object subject, Object value) {

}

@Override
public TypeInfo<?> getTypeInfo(InheritingConfiguration configuration) {
return TypeInfoRegistry.typeInfoFor(type, configuration);
}

@Override
public Type getGenericType() {
return type;
}
}

static class MethodMutator extends AbstractMethodInfo implements Mutator {
MethodMutator(Class<?> initialType, Method method, String name) {
super(initialType, method, name);
Expand Down
Loading