
net.projectmonkey.object.mapper.construction.PopulationService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of object-mapper Show documentation
Show all versions of object-mapper Show documentation
Object mapping implementation written as an alternative to modelmapper which is able to support inheritance, handles flattening / expanding in a precise way, and is extensible / configurable
The newest version!
package net.projectmonkey.object.mapper.construction;
import net.projectmonkey.object.mapper.analysis.result.PropertyMapping;
import net.projectmonkey.object.mapper.analysis.result.PropertyPath;
import net.projectmonkey.object.mapper.analysis.result.PropertyPathElement;
import net.projectmonkey.object.mapper.construction.converter.Converter;
import net.projectmonkey.object.mapper.construction.converter.resolver.ConverterResolver;
import net.projectmonkey.object.mapper.construction.converter.resolver.StandardConverterResolver;
import net.projectmonkey.object.mapper.construction.postprocessor.PostProcessor;
import net.projectmonkey.object.mapper.construction.postprocessor.resolver.PostProcessorResolver;
import net.projectmonkey.object.mapper.construction.postprocessor.resolver.StandardPostProcessorResolver;
import net.projectmonkey.object.mapper.construction.rule.MappingRule;
import net.projectmonkey.object.mapper.construction.rule.resolver.MappingRuleResolver;
import net.projectmonkey.object.mapper.construction.rule.resolver.StandardRuleResolver;
import net.projectmonkey.object.mapper.construction.type.GenericTypeUtils;
import net.projectmonkey.object.mapper.context.ExecutionContext;
import net.projectmonkey.object.mapper.util.CollectionUtil;
import net.projectmonkey.object.mapper.util.Logger;
import net.projectmonkey.object.mapper.util.Types;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
/*
*
* * Copyright 2012 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.
*
*/
/**
* The main coordinator for object construction
*
* @author Andy Moody
*/
public class PopulationService
{
private static final Logger logger = Logger.getLogger(PopulationService.class);
private static final ConverterResolver converterResolver = StandardConverterResolver.INSTANCE;
private static final MappingRuleResolver ruleResolver = StandardRuleResolver.INSTANCE;
private static final PostProcessorResolver postProcessorResolver = StandardPostProcessorResolver.INSTANCE;
/**
* Constructs and populates an object of destinationType
* using the provided mappings to determine what to populate.
*
* N.B. The mappings for a given property may be overridden if
* it is detected that a different sub class is required since the
* mappings may vary from those available during analysis of the class
* structure alone.
*
* @param destinationType
* @param source
* @param mappings
* @return The constructed and populated object
*/
public T constructAndPopulate(Class destinationType, S source, final List mappings)
{
T destination = null;
if(source != null)
{
RootPopulationContext context = new RootPopulationContext(source, destinationType);
destination = constructAndPopulate(source, mappings, context);
}
return destination;
}
/**
* Constructs and populates an object of destinationType
* using the provided mappings and context to determine what to populate.
*
* N.B. The mappings for a given property may be overridden if
* it is detected that a different sub class is required since the
* mappings may vary from those available during analysis of the class
* structure alone.
*
* @param source
* @param mappings
* @return The constructed and populated object
*/
public T constructAndPopulate(S source, final List mappings, PopulationContext context)
{
T destination = null;
if(source != null)
{
GenericTypeUtils.addDestinationTypeArguments(context);
destination = constructAndPopulate(mappings, context);
}
return destination;
}
/**
* Populates the provided destination with values from the source
* according to the provided mappings.
*
* N.B. The mappings for a given property may be overridden if
* it is detected that a different sub class is required since the
* mappings may vary from those available during analysis of the class
* structure alone.
*
* Where intermediate objects exist in the destination and they are of the correct type
* they will be left in place and populated as per the mappings.
*
* Where intermediate objects are null in the destination or exist in the destination
* but are of an incorrect type they will be created and populated as per the mappings.
*
* @param destination
* @param source
* @param mappings
*/
public void populate(final T destination, final S source, final List mappings)
{
if(source != null && destination != null)
{
RootPopulationContext context = new RootPopulationContext(source, Types.deProxy(destination.getClass()));
populate(destination, mappings, context);
}
}
/**
* Populates the provided destination with values from the source
* according to the provided mappings and using the provided context.
*
* N.B. The mappings for a given property may be overridden if
* it is detected that a different sub class is required since the
* mappings may vary from those available during analysis of the class
* structure alone.
*
* Where intermediate objects exist in the destination and they are of the correct type
* they will be left in place and populated as per the mappings.
*
* Where intermediate objects are null in the destination or exist in the destination
* but are of an incorrect type they will be created and populated as per the mappings.
*
* @param destination
* @param mappings
*/
public void populate(final T destination, final List mappings, PopulationContext context)
{
if(context.getSource() != null && destination != null)
{
context.setDestination(destination);
constructAndPopulate(mappings, context);
}
}
public T instantiate(Class type)
{
try
{
Constructor constructor = type.getDeclaredConstructor();
if(!constructor.isAccessible())
{
constructor.setAccessible(true);
}
return constructor.newInstance();
}
catch(Exception e)
{
throw new RuntimeException("Unable to create destination " + type, e);
}
}
private T constructAndPopulate(final List children, final PopulationContext context)
{
T destination;
S source = context.getSource();
if(source != null)
{
Class extends T> destinationType = context.getDestinationType();
Converter converter = converterResolver.getFirstAppropriateConverter(context);
if(converter != null)
{
logger.debug("Converting %s to %s using converter %s", source, destinationType, converter);
destination = getOrCreate(destinationType, context, new ConverterConstructionMechanism(converter));
}
else
{
logger.debug("No converter found for %s to %s, converting manually", source, destinationType);
destination = getOrCreate(destinationType, context, new InstantiationMechanism(source, children));
}
context.setDestination(destination);
}
PostProcessor postProcessor = postProcessorResolver.getFirstAppropriatePostProcessor(context);
if(postProcessor != null)
{
postProcessor.postProcess(context);
}
return (T) context.getDestination(); //return the destination from the context since it could be changed by the post processor
}
private T getOrCreate(Class type, PopulationContext context, ObjectConstructionMechanism mechanism)
{
T destination;
T previouslyPopulatedDestination = (T) ExecutionContext.getDestination(context);
if(previouslyPopulatedDestination != null)
{
logger.debug("previously populated destination found for "+context+" returning "+previouslyPopulatedDestination);
destination = previouslyPopulatedDestination;
}
else
{
destination = (T) context.getDestination();
if(destination == null || mechanism.alwaysConstruct(context))
{
logger.debug("Construction required for "+context+" with existing destination "+destination+" constructing");
destination = mechanism.construct(type, context);
context.setDestination(destination);
}
ExecutionContext.registerDestination(context, destination);
mechanism.populate(destination, context);
}
return destination;
}
private boolean shouldMap(PropertyPopulationContext context)
{
List> rules = ruleResolver.getAllApplicableRules(context);
boolean map = true;
for(int i = 0; i < rules.size() && map; i++)
{
MappingRule rule = rules.get(i);
map = map && rule.isIncluded(context);
}
return map;
}
private class InstantiationMechanism implements ObjectConstructionMechanism
{
private Object source;
private List children;
public InstantiationMechanism(final Object source, final List children)
{
this.source = source;
this.children = children;
}
@Override
public T construct(final Class type, PopulationContext, ?> context)
{
return instantiate(type);
}
@Override
public void populate(final Object destination, final PopulationContext context)
{
for(PropertyMapping mapping : children)
{
PropertyPathElement destPath = mapping.getDestinationProperty();
PropertyPathElement sourcePath = mapping.getSourceProperty();
PopulationContext contextForPopulation = getOrCreateIntermediateObjects(context, source, mapping);
Object currentSource = contextForPopulation.getSource();
Object value = currentSource == null ? null : sourcePath.getValue(currentSource);
Object currentDestination = contextForPopulation.getDestination();
Object currentDestinationValue = currentDestination == null ? null : destPath.getValue(currentDestination);
Class> destinationType = GenericTypeUtils.INSTANCE.resolveActualDestinationType(contextForPopulation, mapping.getSourcePath(), mapping.getDestinationPath());
PropertyPopulationContext propertyPopulationContext = new PropertyPopulationContext(contextForPopulation, destPath, sourcePath, value, destinationType);
if(currentDestinationValue != null && destinationType.isAssignableFrom(currentDestinationValue.getClass()))
{
propertyPopulationContext.setDestination(currentDestinationValue);
}
if(shouldMap(propertyPopulationContext))
{
if(currentDestination != null)
{
Object populatedDestinationValue = constructAndPopulate(mapping.getChildren(), propertyPopulationContext);
destPath.setValue(populatedDestinationValue, currentDestination);
}
}
}
}
@Override
public boolean alwaysConstruct(final PopulationContext context)
{
return false;
}
private PopulationContext getOrCreateIntermediateObjects(final PopulationContext context, final Object source, PropertyMapping mapping)
{
PopulationContext contextForPopulation = context;
List sourceIntermediatePaths = mapping.getSourceIntermediatePaths();
Object currentSource = source;
if(CollectionUtil.hasElements(sourceIntermediatePaths))
{
for (PropertyPath intermediatePath : sourceIntermediatePaths)
{
if(currentSource != null)
{
currentSource = intermediatePath.getProperty().getValue(currentSource);
}
}
contextForPopulation = new PropertyPopulationContext(context, context.getDestinationProperty(), context.getSourceProperty(), currentSource, context.getDestinationType());
contextForPopulation.setDestination(context.getDestination());
}
if(!(contextForPopulation instanceof RootPopulationContext))
{
GenericTypeUtils.addDestinationTypeArguments(contextForPopulation);
}
List destinationIntermediatePaths = mapping.getDestinationIntermediatePaths();
if(CollectionUtil.hasElements(destinationIntermediatePaths))
{
for (PropertyPath intermediatePath : destinationIntermediatePaths)
{
PopulationContext previousContext = contextForPopulation;
PropertyPathElement intermediatePathElement = intermediatePath.getProperty();
contextForPopulation = new PropertyPopulationContext(previousContext, intermediatePathElement, context.getSourceProperty(), currentSource, intermediatePathElement.getType());
Object intermediateDestination = constructAndPopulate(new ArrayList(), contextForPopulation);
contextForPopulation.getDestinationProperty().setValue(intermediateDestination, previousContext.getDestination());
}
}
return contextForPopulation;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy