Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.modelmapper.internal.ExplicitMappingBuilder Maven / Gradle / Ivy
/*
* Copyright 2011-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.modelmapper.internal;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.modelmapper.Condition;
import org.modelmapper.ConfigurationException;
import org.modelmapper.Converter;
import org.modelmapper.PropertyMap;
import org.modelmapper.Provider;
import org.modelmapper.builder.ConditionExpression;
import org.modelmapper.internal.ExplicitMappingVisitor.VisitedMapping;
import org.modelmapper.internal.PropertyInfoImpl.FieldPropertyInfo;
import org.modelmapper.internal.PropertyInfoImpl.MethodAccessor;
import org.modelmapper.internal.PropertyInfoImpl.ValueReaderPropertyInfo;
import org.modelmapper.internal.PropertyInfoImpl.ValueWriterPropertyInfo;
import org.modelmapper.internal.util.Assert;
import org.modelmapper.internal.util.Members;
import org.modelmapper.internal.util.Types;
import org.modelmapper.spi.PropertyType;
import org.modelmapper.spi.ValueReader;
import org.modelmapper.spi.ValueWriter;
import org.objectweb.asm.ClassReader;
/**
* Builds explicit property mappings.
*
* @author Jonathan Halterman
*/
public class ExplicitMappingBuilder implements ConditionExpression {
private static final Pattern DOT_PATTERN = Pattern.compile("\\.");
private static Method PROPERTY_MAP_CONFIGURE;
private final Class sourceType;
private final Class destinationType;
private final InheritingConfiguration configuration;
public volatile S source;
public volatile D destination;
private final Errors proxyErrors = new Errors();
private final Errors errors = new Errors();
private List visitedMappings;
private final Map proxyInterceptors = new IdentityHashMap();
private final Set propertyMappings = new HashSet();
/** Per mapping state */
private int currentMappingIndex;
private VisitedMapping currentMapping;
private MappingOptions options = new MappingOptions();
private List sourceAccessors;
private List destinationMutators;
private Object sourceConstant;
static {
PROPERTY_MAP_CONFIGURE = Members.methodFor(PropertyMap.class, "configure",
ExplicitMappingBuilder.class);
PROPERTY_MAP_CONFIGURE.setAccessible(true);
}
static class MappingOptions {
Condition, ?> condition;
Converter, ?> converter;
Provider> provider;
int skipType;
boolean mapFromSource;
}
static Collection build(Class sourceType, Class destinationType,
InheritingConfiguration configuration, PropertyMap propertyMap) {
return new ExplicitMappingBuilder(sourceType, destinationType, configuration)
.build(propertyMap);
}
ExplicitMappingBuilder(Class sourceType, Class destinationType,
InheritingConfiguration configuration) {
this.sourceType = sourceType;
this.destinationType = destinationType;
this.configuration = configuration;
}
public D skip() {
map();
options.skipType = 1;
return destination;
}
public void skip(Object destination) {
map(destination);
options.skipType = 2;
}
public void skip(Object source, Object destination) {
map(source, destination);
options.skipType = 3;
}
public D map() {
saveLastMapping();
getNextMapping();
return destination;
}
public D map(Object subject) {
saveLastMapping();
getNextMapping();
recordSourceValue(subject);
return destination;
}
public void map(Object source, Object destination) {
saveLastMapping();
getNextMapping();
recordSourceValue(source);
}
public T source(String sourcePropertyPath) {
if (sourcePropertyPath == null)
errors.errorNullArgument("sourcePropertyPath");
if (sourceAccessors != null)
saveLastMapping();
String[] propertyNames = DOT_PATTERN.split(sourcePropertyPath);
sourceAccessors = new ArrayList(propertyNames.length);
ValueReader> valueReader = configuration.valueAccessStore.getFirstSupportedReader(sourceType);
if (valueReader != null)
for (String propertyName : propertyNames)
sourceAccessors.add(ValueReaderPropertyInfo.create(valueReader, propertyName));
else {
Accessor accessor = null;
for (String propertyName : propertyNames) {
Class> propertyType = accessor == null ? sourceType : accessor.getType();
accessor = PropertyInfoRegistry.accessorFor(propertyType, propertyName, configuration);
if (accessor == null) {
errors.errorInvalidSourcePath(sourcePropertyPath, propertyType, propertyName);
return null;
}
sourceAccessors.add(accessor);
}
}
return null;
}
public Object destination(String destPropertyPath) {
if (destPropertyPath == null)
errors.errorNullArgument("destPropertyPath");
String[] propertyNames = DOT_PATTERN.split(destPropertyPath);
destinationMutators = new ArrayList(propertyNames.length);
ValueWriter> valueWriter = configuration.valueMutateStore.getFirstSupportedWriter(destinationType);
if (valueWriter != null)
for (String propertyName : propertyNames)
destinationMutators.add(ValueWriterPropertyInfo.create(valueWriter, propertyName));
else {
Mutator mutator = null;
for (String propertyName : propertyNames) {
Class> propertyType = mutator == null ? destinationType : mutator.getType();
mutator = PropertyInfoRegistry.mutatorFor(propertyType, propertyName, configuration);
if (mutator == null) {
errors.errorInvalidDestinationPath(destPropertyPath, propertyType, propertyName);
return null;
}
destinationMutators.add(mutator);
}
}
return null;
}
public ConditionExpression using(Converter, ?> converter) {
saveLastMapping();
if (converter == null)
errors.errorNullArgument("converter");
Assert.state(options.converter == null, "using() can only be called once per mapping.");
options.converter = converter;
return this;
}
public ConditionExpression when(Condition, ?> condition) {
saveLastMapping();
if (condition == null)
errors.errorNullArgument("condition");
Assert.state(options.condition == null, "when() can only be called once per mapping.");
options.condition = condition;
return this;
}
public ConditionExpression with(Provider> provider) {
saveLastMapping();
if (provider == null)
errors.errorNullArgument("provider");
Assert.state(options.provider == null, "withProvider() can only be called once per mapping.");
options.provider = provider;
return this;
}
/**
* Builds and returns all property mappings defined in the {@code propertyMap}.
*/
Collection build(PropertyMap propertyMap) {
try {
PROPERTY_MAP_CONFIGURE.invoke(propertyMap, this);
saveLastMapping();
} catch (IllegalAccessException e) {
errors.errorAccessingConfigure(e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof ConfigurationException)
throw (ConfigurationException) cause;
else
errors.addMessage(cause, "Failed to configure mappings");
} catch (NullPointerException e) {
if (proxyErrors.hasErrors()) {
throw proxyErrors.toException();
}
throw e;
}
errors.throwConfigurationExceptionIfErrorsExist();
return propertyMappings;
}
/**
* Visits the {@code propertyMap} and captures and validates mappings.
*/
public void visitPropertyMap(PropertyMap propertyMap) {
String propertyMapClassName = propertyMap.getClass().getName();
try {
ClassReader cr = new ClassReader(propertyMap.getClass().getClassLoader().getResourceAsStream(
propertyMapClassName.replace('.', '/') + ".class"));
ExplicitMappingVisitor visitor = new ExplicitMappingVisitor(errors, configuration,
propertyMapClassName, destinationType.getName(), propertyMap.getClass().getClassLoader());
cr.accept(visitor, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
visitedMappings = visitor.mappings;
} catch (IOException e) {
errors.errorReadingClass(e, propertyMapClassName);
}
errors.throwConfigurationExceptionIfErrorsExist();
createProxies();
}
/**
* Creates the source and destination proxy models.
*/
private void createProxies() {
source = createProxy(sourceType);
destination = createProxy(destinationType);
for (VisitedMapping mapping : visitedMappings) {
createAccessorProxies(source, mapping.sourceAccessors);
createAccessorProxies(destination, mapping.destinationAccessors);
}
}
private void createAccessorProxies(Object proxy, List accessors) {
for (Accessor accessor : accessors) {
if (accessor instanceof MethodAccessor) {
ExplicitMappingInterceptor interceptor = proxyInterceptors.get(proxy);
String methodName = accessor.getMember().getName();
proxy = interceptor.methodProxies.get(methodName);
if (proxy == null) {
proxy = createProxy(accessor.getType());
interceptor.methodProxies.put(methodName, proxy);
}
} else if (accessor instanceof FieldPropertyInfo) {
FieldPropertyInfo field = (FieldPropertyInfo) accessor;
Object nextProxy = field.getValue(proxy);
if (nextProxy == null) {
nextProxy = createProxy(field.getType());
field.setValue(proxy, nextProxy);
}
proxy = nextProxy;
}
}
}
public final class ExplicitMappingInterceptor implements InvocationHandler {
private final Map methodProxies = new HashMap();
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
if (args.length == 1) {
sourceConstant = args[0];
if (sourceConstant != null && sourceConstant == source)
errors.missingSource();
}
return methodProxies.get(method.getName());
}
}
private T createProxy(Class type) {
ExplicitMappingInterceptor interceptor = new ExplicitMappingInterceptor();
try {
T proxy = ProxyFactory.proxyFor(type, interceptor, proxyErrors, configuration.isUseOSGiClassLoaderBridging());
proxyInterceptors.put(proxy, interceptor);
return proxy;
} catch (ErrorsException e) {
return null;
}
}
private void getNextMapping() {
if (currentMappingIndex < visitedMappings.size())
currentMapping = visitedMappings.get(currentMappingIndex++);
}
private void recordSourceValue(Object sourceValue) {
if (sourceValue != null) {
if (sourceValue == source)
options.mapFromSource = true;
else if (!Types.isProxied(sourceValue.getClass()))
sourceConstant = sourceValue;
}
}
/**
* Validates the current mapping that was recorded via a MapExpression.
*/
private void validateRecordedMapping() {
if (currentMapping.destinationMutators == null || currentMapping.destinationMutators.isEmpty())
errors.missingDestination();
// If mapping a field without a source
else if (options.skipType == 0
&& (currentMapping.sourceAccessors == null || currentMapping.sourceAccessors.isEmpty())
&& currentMapping.destinationMutators.get(currentMapping.destinationMutators.size() - 1)
.getPropertyType()
.equals(PropertyType.FIELD) && options.converter == null && !options.mapFromSource
&& sourceConstant == null)
errors.missingSource();
else if (options.skipType == 2 && options.condition != null)
errors.conditionalSkipWithoutSource();
}
private void saveLastMapping() {
if (currentMapping != null) {
try {
MappingImpl mapping;
if (currentMapping.sourceAccessors.isEmpty())
currentMapping.sourceAccessors = sourceAccessors;
if (currentMapping.destinationMutators.isEmpty())
currentMapping.destinationMutators = destinationMutators;
validateRecordedMapping();
if (!errors.hasErrors()) {
if (options.mapFromSource)
mapping = new SourceMappingImpl(sourceType, currentMapping.destinationMutators,
options);
else if (currentMapping.sourceAccessors == null)
mapping = new ConstantMappingImpl(sourceConstant, currentMapping.destinationMutators,
options);
else
mapping = new PropertyMappingImpl(currentMapping.sourceAccessors,
currentMapping.destinationMutators, options);
if (!propertyMappings.add(mapping))
errors.duplicateMapping(mapping.getLastDestinationProperty());
}
} finally {
currentMapping = null;
options = new MappingOptions();
sourceAccessors = null;
sourceConstant = null;
}
}
}
}