All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.projectmonkey.object.mapper.construction.PopulationService Maven / Gradle / Ivy

Go to download

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 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