From d4b0f3324b433e0c3e652ae816cb8e29fbf03348 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 18 Feb 2017 01:25:33 +0100 Subject: [PATCH] #1057 Add some explanatory comments / Javadoc --- .../ap/internal/model/BeanMappingMethod.java | 11 +- .../NestedTargetPropertyMappingHolder.java | 153 ++++++++++++++++-- 2 files changed, 149 insertions(+), 15 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index b9ba81639..c9e438bd6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -147,6 +147,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { applyParameterNameBasedMapping(); } + // Process the unprocessed defined targets handleUnprocessedDefinedTargets(); // report errors on unmapped properties @@ -220,11 +221,13 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } /** - * If there were nested defined targets that have not been handled. The we need to process them at he end. + * If there were nested defined targets that have not been handled. Then we need to process them at the end. */ private void handleUnprocessedDefinedTargets() { Iterator>> iterator = unprocessedDefinedTargets.entrySet().iterator(); + // For each of the unprocessed defined targets forge a mapping for each of the + // method source parameters. The generated mappings are not going to use forged name based mappings. while ( iterator.hasNext() ) { Entry> entry = iterator.next(); String propertyName = entry.getKey(); @@ -315,6 +318,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { boolean errorOccurred = false; Set handledTargets = new HashSet(); + // first we have to handle nested target mappings if ( method.getMappingOptions().hasNestedTargetReferences() ) { handleDefinedNestedTargetMapping( handledTargets ); } @@ -355,6 +359,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { unprocessedSourceParameters.removeAll( holder.getProcessedSourceParameters() ); propertyMappings.addAll( holder.getPropertyMappings() ); handledTargets.addAll( holder.getHandledTargets() ); + // Store all the unprocessed defined targets. for ( Entry> entry : holder.getUnprocessedDefinedTarget().entrySet() ) { if ( entry.getValue().isEmpty() ) { continue; @@ -424,6 +429,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } // its a constant + // if we have an unprocessed target that means that it most probably is nested and we should + // not generated any mapping for it now. Eventually it will be done though else if ( mapping.getConstant() != null && !unprocessedDefinedTargets.containsKey( propertyName ) ) { propertyMapping = new ConstantMappingBuilder() @@ -441,6 +448,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } // its an expression + // if we have an unprocessed target that means that it most probably is nested and we should + // not generated any mapping for it now. Eventually it will be done though else if ( mapping.getJavaExpression() != null && !unprocessedDefinedTargets.containsKey( propertyName ) ) { propertyMapping = new JavaExpressionMappingBuilder() diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 3788cc603..53223f1b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -128,28 +128,39 @@ public class NestedTargetPropertyMappingHolder { Set handledTargets = new HashSet(); List propertyMappings = new ArrayList(); + // first we group by the first property in the target properties and for each of those + // properties we get the new mappings as if the first property did not exist. GroupedTargetReferences groupedByTP = groupByTargetReferences( method.getMappingOptions() ); Map> unprocessedDefinedTarget = new HashMap>(); for ( Map.Entry> entryByTP : groupedByTP.poppedTargetReferences.entrySet() ) { PropertyEntry targetProperty = entryByTP.getKey(); + //Now we are grouping the already popped mappings by the source parameter(s) of the method GroupedBySourceParameters groupedBySourceParam = groupBySourceParameter( entryByTP.getValue(), groupedByTP.singleTargetReferences.get( targetProperty ) ); boolean forceUpdateMethod = groupedBySourceParam.groupedBySourceParameter.keySet().size() > 1; + // All not processed mappings that should have been applied to all are part of the unprocessed + // defined targets unprocessedDefinedTarget.put( targetProperty, groupedBySourceParam.notProcessedAppliesToAll ); for ( Map.Entry> entryByParam : groupedBySourceParam .groupedBySourceParameter.entrySet() ) { Parameter sourceParameter = entryByParam.getKey(); + // Lastly we need to group by the source references. This will allow us to actually create + // the next mappings by popping source elements GroupedSourceReferences groupedSourceReferences = groupByPoppedSourceReferences( entryByParam.getValue(), groupedByTP.singleTargetReferences.get( targetProperty ) ); + // For all the groupedBySourceReferences we need to create property mappings + // from the Mappings and not restrict on the defined mappings (allow to forge name based mapping) + // if we have composite methods i.e. more then 2 parameters then we have to force a creation + // of an update method in our generation for ( Map.Entry> entryBySP : groupedSourceReferences .groupedBySourceReferences .entrySet() ) { @@ -180,6 +191,9 @@ public class NestedTargetPropertyMappingHolder { handledTargets.add( entryByTP.getKey().getName() ); } + // For the nonNested mappings (assymetric) Mappings we also forge mappings + // However, here we do not forge name based mappings and we only + // do update on the defined Mappings. if ( !groupedSourceReferences.nonNested.isEmpty() ) { MappingOptions nonNestedOptions = MappingOptions.forMappingsOnly( groupByTargetName( groupedSourceReferences.nonNested ), @@ -220,24 +234,43 @@ public class NestedTargetPropertyMappingHolder { * The target references are popped. The {@code List<}{@link Mapping}{@code >} are keyed on the unique first * entries of the target references. * - * So, take + *

+ *

+ * Group all target references by their first property and for each such mapping use a new one where the + * first property will be removed from it. If a {@link org.mapstruct.Mapping} cannot be popped, i.e. it + * contains a non nested target property just keep it as is (this is usually needed to control how an + * intermediary level can be mapped). * - * targetReference 1: propertyEntryX.propertyEntryX1.propertyEntryX1a - * targetReference 2: propertyEntryX.propertyEntryX2 - * targetReference 3: propertyEntryY.propertyY1 - * targetReference 4: propertyEntryZ + *

+ *

+ * We start with the following mappings: * - * will be popped and grouped into entries: + *

+         * {@literal @}Mapping(target = "fish.kind", source = "fish.type"),
+         * {@literal @}Mapping(target = "fish.name", ignore = true),
+         * {@literal @}Mapping(target = "ornament", ignore = true ),
+         * {@literal @}Mapping(target = "material.materialType", source = "material"),
+         * {@literal @}Mapping(target = "document", source = "report"),
+         * {@literal @}Mapping(target = "document.organisation.name", source = "report.organisationName")
+         * 
* - * propertyEntryX - List ( targetReference1: propertyEntryX1.propertyEntryX1a, - * targetReference2: propertyEntryX2 ) - * propertyEntryY - List ( targetReference1: propertyEntryY1 ) + * We will get this: * - * The key will be the former top level property, the MappingOptions will contain the remainders. + *
+         * // All target references are popped and grouped by their first property
+         * GroupedTargetReferences.poppedTargetReferences {
+         *     fish:     {@literal @}Mapping(target = "kind", source = "fish.type"),
+         *               {@literal @}Mapping(target = "name", ignore = true);
+         *     material: {@literal @}Mapping(target = "materialType", source = "material");
+         *     document: {@literal @}Mapping(target = "organization.name", source = "report.organizationName");
+         * }
          *
-         * If the target reference cannot be popped it is stored in a different map. That looks like:
-         *
-         * propertyEntryZ - List ( targetReference4: propertyEntryZ )
+         * //This references are not nested and we they stay as they were
+         * GroupedTargetReferences.singleTargetReferences {
+         *     document: {@literal @}Mapping(target = "document", source = "report");
+         *     ornament: {@literal @}Mapping(target = "ornament", ignore = true );
+         * }
+         * 
* * @param mappingOptions that need to be used to create the {@link GroupedTargetReferences} * @@ -275,6 +308,78 @@ public class NestedTargetPropertyMappingHolder { * Note: this method is used for forging nested update methods. For that purpose, the same method with all * joined mappings should be generated. See also: NestedTargetPropertiesTest#shouldMapNestedComposedTarget * + * Mappings: + *
+         * {@literal @}Mapping(target = "organization.name", source = "report.organizationName");
+         * 
+ * + * singleTargetReferences: + *
+         * {@literal @}Mapping(target = "document", source = "report");
+         * 
+ * + * We assume that all properties belong to the same source parameter (fish). We are getting this in return: + * + *
+         * GroupedBySourceParameters.groupedBySourceParameter {
+         *     fish: {@literal @}Mapping(target = "organization.name", source = "report.organizationName");
+         * }
+         *
+         * GroupedBySourceParameters.notProcessedAppliesToAll {} //empty
+         *
+         * 
+ * + * Notice how the {@code singleTargetReferences} are missing. They are used for situations when there are + * mappings without source. Such as: + * Mappings: + *
+         * {@literal @}Mapping(target = "organization.name", expression="java(\"Dunno\")");
+         * 
+ * + * singleTargetReferences: + *
+         * {@literal @}Mapping(target = "document", source = "report");
+         * 
+ * + * The mappings have no source reference so we cannot extract the source parameter from them. When mappings + * have no source properties then we apply those to all of them. In this case we have a single target + * reference that can provide a source parameter. So we will get: + *
+         * GroupedBySourceParameters.groupedBySourceParameter {
+         *     fish: {@literal @}Mapping(target = "organization.name", expression="java(\"Dunno\")");
+         * }
+         *
+         * GroupedBySourceParameters.notProcessedAppliesToAll {} //empty
+         * 
+ * + *

+ * See also how the {@code singleTargetReferences} are not part of the mappings. They are used only to + * make sure that their source parameter is taken into consideration in the next step. + * + *

+ * The {@code notProcessedAppliesToAll} contains all Mappings that should have been applied to all but have not + * because there were no other mappings that we could have used to pass them along. These + * {@link org.mapstruct.Mapping}(s) are used later on for normal mapping. + * + *

+ * If we only had: + *

+         * {@literal @}Mapping(target = "document", source = "report");
+         * {@literal @}Mapping(target = "ornament", ignore = true );
+         * 
+ * + * Then we only would have had: + *
+         * GroupedBySourceParameters.notProcessedAppliesToAll {
+         * {@literal @}Mapping(target = "document", source = "report");
+         * {@literal @}Mapping(target = "ornament", ignore = true );
+         * }
+         * 
+ * + * These mappings will be part of the {@code GroupedBySourceParameters.notProcessedAppliesToAll} and are + * used to be passed to the normal defined mapping. + * + * * @param mappings that mappings that need to be used for the grouping * @param singleTargetReferences a List containing all non-nested mappings for the same grouped target * property as the {@code mappings} @@ -318,6 +423,26 @@ public class NestedTargetPropertyMappingHolder { * Creates a nested grouping by popping the source mappings. See the description of the class to see what is * generated. * + * Mappings: + *
+         * {@literal @}Mapping(target = "organization.name", source = "report.organizationName");
+         * 
+ * + * singleTargetReferences: + *
+         * {@literal @}Mapping(target = "document", source = "report");
+         * 
+ * + * And we get: + * + *
+         * GroupedSourceReferences.groupedBySourceReferences {
+         *     report: {@literal @}Mapping(target = "organization.name", source = "organizationName");
+         * }
+         * 
+ * + * + * * @param mappings the list of {@link Mapping} that needs to be used for grouping on popped source references * @param singleTargetReferences the single target references that match the source mappings * @@ -484,7 +609,7 @@ public class NestedTargetPropertyMappingHolder { * sourceReference 5: propertyEntryZ2 * * - *
+ *

* If Mappings that should apply to all were found, but no grouping was found, they will be located in a * different list: */