#1011 Using ForgedMethods to forge nested target mappings

This commit is contained in:
sjaakd 2017-01-21 11:14:11 +01:00
parent 12fcf7ce87
commit 1406c0b6db
28 changed files with 1137 additions and 653 deletions

View File

@ -27,6 +27,7 @@ import org.mapstruct.ap.internal.model.HelperMethod;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.MappingOptions;
/**
* HelperMethod that creates a {@link java.text.DecimalFormat}
@ -63,4 +64,8 @@ public class CreateDecimalFormat extends HelperMethod {
return returnType;
}
@Override
public MappingOptions getMappingOptions() {
return MappingOptions.empty();
}
}

View File

@ -19,12 +19,9 @@
package org.mapstruct.ap.internal.model;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.last;
import static org.mapstruct.ap.internal.util.Strings.getSaveVariableName;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@ -42,7 +39,6 @@ import javax.tools.Diagnostic;
import org.mapstruct.ap.internal.model.PropertyMapping.ConstantMappingBuilder;
import org.mapstruct.ap.internal.model.PropertyMapping.JavaExpressionMappingBuilder;
import org.mapstruct.ap.internal.model.PropertyMapping.PropertyMappingBuilder;
import org.mapstruct.ap.internal.model.assignment.Assignment;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer;
@ -50,6 +46,7 @@ import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBui
import org.mapstruct.ap.internal.model.source.ForgedMethod;
import org.mapstruct.ap.internal.model.source.ForgedMethodHistory;
import org.mapstruct.ap.internal.model.source.Mapping;
import org.mapstruct.ap.internal.model.source.MappingOptions;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.PropertyEntry;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
@ -72,7 +69,7 @@ import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
*
* @author Gunnar Morling
*/
public class BeanMappingMethod extends MappingMethod {
public class BeanMappingMethod extends ContainerMappingMethod {
private final List<PropertyMapping> propertyMappings;
private final Map<String, List<PropertyMapping>> mappingsByParameter;
@ -80,7 +77,6 @@ public class BeanMappingMethod extends MappingMethod {
private final MethodReference factoryMethod;
private final boolean mapNullToDefault;
private final Type resultType;
private final NestedTargetObjects nestedTargetObjects;
private final boolean overridden;
public static class Builder {
@ -94,7 +90,6 @@ public class BeanMappingMethod extends MappingMethod {
private NullValueMappingStrategyPrism nullValueMappingStrategy;
private SelectionParameters selectionParameters;
private final Set<String> existingVariableNames = new HashSet<String>();
private NestedTargetObjects nestedTargetObjects;
private Map<String, List<Mapping>> methodMappings;
private SingleMappingByTargetPropertyNameFunction singleMapping;
@ -105,28 +100,21 @@ public class BeanMappingMethod extends MappingMethod {
public Builder souceMethod(SourceMethod sourceMethod) {
singleMapping = new SourceMethodSingleMapping( sourceMethod );
return setupMethodWithMapping( sourceMethod, sourceMethod.getMappingOptions().getMappings() );
return setupMethodWithMapping( sourceMethod );
}
public Builder forgedMethod(Method sourceMethod) {
public Builder forgedMethod(Method method ) {
singleMapping = new EmptySingleMapping();
return setupMethodWithMapping( sourceMethod, Collections.<String, List<Mapping>>emptyMap() );
return setupMethodWithMapping( method );
}
private Builder setupMethodWithMapping(Method sourceMethod, Map<String, List<Mapping>> mappings) {
private Builder setupMethodWithMapping(Method sourceMethod) {
this.method = sourceMethod;
this.methodMappings = mappings;
this.methodMappings = sourceMethod.getMappingOptions().getMappings();
CollectionMappingStrategyPrism cms = sourceMethod.getMapperConfiguration().getCollectionMappingStrategy();
Map<String, Accessor> accessors = method.getResultType().getPropertyWriteAccessors( cms );
this.targetProperties = accessors.keySet();
this.nestedTargetObjects = new NestedTargetObjects.Builder()
.existingVariableNames( existingVariableNames )
.mappings( mappings )
.mappingBuilderContext( ctx )
.sourceMethod( method )
.build();
this.unprocessedTargetProperties = new LinkedHashMap<String, Accessor>( accessors );
for ( Parameter sourceParameter : method.getSourceParameters() ) {
unprocessedSourceParameters.add( sourceParameter );
@ -147,16 +135,19 @@ public class BeanMappingMethod extends MappingMethod {
public BeanMappingMethod build() {
// map properties with mapping
boolean mappingErrorOccured = handleDefinedSourceMappings();
boolean mappingErrorOccured = handleDefinedMappings();
if ( mappingErrorOccured ) {
return null;
}
if ( !method.getMappingOptions().isRestrictToDefinedMappings() ) {
// map properties without a mapping
applyPropertyNameBasedMapping();
// map parameters without a mapping
applyParameterNameBasedMapping();
}
// report errors on unmapped properties
reportErrorForUnmappedTargetPropertiesIfRequired();
@ -223,10 +214,8 @@ public class BeanMappingMethod extends MappingMethod {
factoryMethod,
mapNullToDefault,
resultType,
existingVariableNames,
beforeMappingMethods,
afterMappingMethods,
nestedTargetObjects
afterMappingMethods
);
}
@ -277,51 +266,82 @@ public class BeanMappingMethod extends MappingMethod {
* It is furthermore checked whether the given mappings are correct. When an error occurs, the method continues
* in search of more problems.
*/
private boolean handleDefinedSourceMappings() {
private boolean handleDefinedMappings() {
boolean errorOccurred = false;
Set<String> handledTargets = new HashSet<String>();
if ( method.getMappingOptions().hasNestedTargetReferences() ) {
handleDefinedNestedTargetMapping( handledTargets );
}
for ( Map.Entry<String, List<Mapping>> entry : methodMappings.entrySet() ) {
for ( Mapping mapping : entry.getValue() ) {
TargetReference targetReference = mapping.getTargetReference();
if ( targetReference.isValid() ) {
if ( !handledTargets.contains( first( targetReference.getPropertyEntries() ).getFullName() ) ) {
if ( handleDefinedMapping( mapping, handledTargets ) ) {
errorOccurred = true;
}
}
}
else if ( reportErrorOnTargetObject( mapping ) ) {
errorOccurred = true;
}
}
}
for ( String handledTarget : handledTargets ) {
// In order to avoid: "Unknown property foo in return type" in case of duplicate
// target mappings
unprocessedTargetProperties.remove( handledTarget );
}
return errorOccurred;
}
private void handleDefinedNestedTargetMapping(Set<String> handledTargets) {
Map<PropertyEntry, MappingOptions> optionsByNestedTarget =
method.getMappingOptions().groupByPoppedTargetReferences();
for ( Entry<PropertyEntry, MappingOptions> entryByTP : optionsByNestedTarget.entrySet() ) {
Map<Parameter, MappingOptions> optionsBySourceParam = entryByTP.getValue().groupBySourceParameter();
for ( Entry<Parameter, MappingOptions> entryByParam : optionsBySourceParam.entrySet() ) {
SourceReference sourceRef = new SourceReference.BuilderFromProperty()
.sourceParameter( entryByParam.getKey() )
.name( entryByTP.getKey().getName() )
.build();
PropertyMapping propertyMapping = new PropertyMappingBuilder()
.mappingContext( ctx )
.sourceMethod( method )
.targetProperty( entryByTP.getKey() )
.targetPropertyName( entryByTP.getKey().getName() )
.sourceReference( sourceRef )
.existingVariableNames( existingVariableNames )
.dependsOn( entryByParam.getValue().collectNestedDependsOn() )
.forgeMethodWithMappingOptions( entryByParam.getValue() )
.build();
unprocessedSourceParameters.remove( sourceRef.getParameter() );
if ( propertyMapping != null ) {
propertyMappings.add( propertyMapping );
}
}
handledTargets.add( entryByTP.getKey().getName() );
}
}
private boolean handleDefinedMapping(Mapping mapping, Set<String> handledTargets) {
boolean errorOccured = false;
PropertyMapping propertyMapping = null;
TargetReference targetRef = mapping.getTargetReference();
String resultPropertyName = null;
if ( targetRef.isValid() ) {
resultPropertyName = first( targetRef.getPropertyEntries() ).getName();
}
if ( !unprocessedTargetProperties.containsKey( resultPropertyName ) ) {
boolean hasReadAccessor =
method.getResultType().getPropertyReadAccessors().containsKey( mapping.getTargetName() );
if ( hasReadAccessor ) {
if ( !mapping.isIgnored() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
mapping.getMirror(),
mapping.getSourceAnnotationValue(),
Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE,
mapping.getTargetName() );
errorOccurred = true;
}
}
else {
ctx.getMessager().printMessage(
method.getExecutable(),
mapping.getMirror(),
mapping.getSourceAnnotationValue(),
Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE,
mapping.getTargetName() );
errorOccurred = true;
}
continue;
}
PropertyEntry targetProperty = last( targetRef.getPropertyEntries() );
PropertyEntry targetProperty = first( targetRef.getPropertyEntries() );
// unknown properties given via dependsOn()?
for ( String dependency : mapping.getDependsOn() ) {
@ -333,7 +353,7 @@ public class BeanMappingMethod extends MappingMethod {
Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON,
dependency
);
errorOccurred = true;
errorOccured = true;
}
}
@ -364,14 +384,12 @@ public class BeanMappingMethod extends MappingMethod {
.existingVariableNames( existingVariableNames )
.dependsOn( mapping.getDependsOn() )
.defaultValue( mapping.getDefaultValue() )
.localTargetVarName( nestedTargetObjects.getLocalVariableName( targetRef ) )
.build();
handledTargets.add( resultPropertyName );
handledTargets.add( targetProperty.getName() );
unprocessedSourceParameters.remove( sourceRef.getParameter() );
}
else {
errorOccurred = true;
continue;
errorOccured = true;
}
}
@ -388,7 +406,6 @@ public class BeanMappingMethod extends MappingMethod {
.selectionParameters( mapping.getSelectionParameters() )
.existingVariableNames( existingVariableNames )
.dependsOn( mapping.getDependsOn() )
.localTargetVarName( nestedTargetObjects.getLocalVariableName( targetRef ) )
.build();
handledTargets.add( mapping.getTargetName() );
}
@ -404,25 +421,45 @@ public class BeanMappingMethod extends MappingMethod {
.targetProperty( targetProperty )
.targetPropertyName( mapping.getTargetName() )
.dependsOn( mapping.getDependsOn() )
.localTargetVarName( nestedTargetObjects.getLocalVariableName( targetRef ) )
.build();
handledTargets.add( mapping.getTargetName() );
}
// remaining are the mappings without a 'source' so, 'only' a date format or qualifiers
if ( propertyMapping != null ) {
propertyMappings.add( propertyMapping );
}
}
return errorOccured;
}
for ( String handledTarget : handledTargets ) {
// In order to avoid: "Unknown property foo in return type" in case of duplicate
// target mappings
unprocessedTargetProperties.remove( handledTarget );
}
private boolean reportErrorOnTargetObject(Mapping mapping) {
boolean errorOccurred = false;
boolean hasReadAccessor
= method.getResultType().getPropertyReadAccessors().containsKey( mapping.getTargetName() );
if ( hasReadAccessor ) {
if ( !mapping.isIgnored() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
mapping.getMirror(),
mapping.getSourceAnnotationValue(),
Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE,
mapping.getTargetName() );
errorOccurred = true;
}
}
else {
ctx.getMessager().printMessage(
method.getExecutable(),
mapping.getMirror(),
mapping.getSourceAnnotationValue(),
Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE,
mapping.getTargetName() );
errorOccurred = true;
}
return errorOccurred;
}
@ -574,9 +611,10 @@ public class BeanMappingMethod extends MappingMethod {
//we handle forged methods differently than the usual source ones. in
if ( method instanceof ForgedMethod ) {
if ( targetProperties.isEmpty() || !unprocessedTargetProperties.isEmpty() ) {
ForgedMethod forgedMethod = (ForgedMethod) this.method;
if ( forgedMethod.isAutoMapping()
&& ( targetProperties.isEmpty() || !unprocessedTargetProperties.isEmpty() ) ) {
if ( forgedMethod.getHistory() == null ) {
Type sourceType = this.method.getParameters().get( 0 ).getType();
@ -628,11 +666,19 @@ public class BeanMappingMethod extends MappingMethod {
MethodReference factoryMethod,
boolean mapNullToDefault,
Type resultType,
Collection<String> existingVariableNames,
List<LifecycleCallbackMethodReference> beforeMappingReferences,
List<LifecycleCallbackMethodReference> afterMappingReferences,
NestedTargetObjects nestedTargetObjects) {
super( method, existingVariableNames, beforeMappingReferences, afterMappingReferences );
List<LifecycleCallbackMethodReference> afterMappingReferences) {
super(
method,
null,
factoryMethod,
mapNullToDefault,
null,
beforeMappingReferences,
afterMappingReferences,
null
);
this.propertyMappings = propertyMappings;
// intialize constant mappings as all mappings, but take out the ones that can be contributed to a
@ -652,7 +698,6 @@ public class BeanMappingMethod extends MappingMethod {
this.factoryMethod = factoryMethod;
this.mapNullToDefault = mapNullToDefault;
this.resultType = resultType;
this.nestedTargetObjects = nestedTargetObjects.init( this.getResultName() );
this.overridden = method.overridesMethod();
}
@ -668,18 +713,12 @@ public class BeanMappingMethod extends MappingMethod {
return mappingsByParameter;
}
public Set<LocalVariable> getLocalVariablesToCreate() {
return this.nestedTargetObjects.localVariables;
}
public Set<NestedLocalVariableAssignment> getNestedLocalVariableAssignments() {
return this.nestedTargetObjects.nestedAssignments;
}
@Override
public boolean isMapNullToDefault() {
return mapNullToDefault;
}
@Override
public boolean isOverridden() {
return overridden;
}
@ -701,7 +740,6 @@ public class BeanMappingMethod extends MappingMethod {
for ( PropertyMapping propertyMapping : propertyMappings ) {
types.addAll( propertyMapping.getImportTypes() );
}
types.addAll( nestedTargetObjects.getImportTypes() );
return types;
}
@ -727,148 +765,45 @@ public class BeanMappingMethod extends MappingMethod {
return sourceParameters;
}
@Override
public MethodReference getFactoryMethod() {
return this.factoryMethod;
}
private static class NestedTargetObjects {
private final Set<LocalVariable> localVariables;
private final Set<NestedLocalVariableAssignment> nestedAssignments;
// local variable names indexed by fullname
private final Map<String, String> localVariableNames;
private Set<Type> getImportTypes() {
Set<Type> importedTypes = new HashSet<Type>();
for ( LocalVariable localVariableToCreate : localVariables ) {
importedTypes.add( localVariableToCreate.getType() );
}
return importedTypes;
@Override
public Type getResultElementType() {
return null;
}
private static class Builder {
private Map<String, List<Mapping>> mappings;
private Set<String> existingVariableNames;
private MappingBuilderContext ctx;
private Method method;
private Builder mappings(Map<String, List<Mapping>> mappings) {
this.mappings = mappings;
return this;
}
private Builder existingVariableNames(Set<String> existingVariableNames) {
this.existingVariableNames = existingVariableNames;
return this;
}
private Builder mappingBuilderContext(MappingBuilderContext ctx) {
this.ctx = ctx;
return this;
}
private Builder sourceMethod(Method method) {
this.method = method;
return this;
}
private NestedTargetObjects build() {
Map<String, PropertyEntry> uniquePropertyEntries = new HashMap<String, PropertyEntry>();
Map<String, String> localVariableNames = new HashMap<String, String>();
Set<LocalVariable> localVariables = new HashSet<LocalVariable>();
// colllect unique local variables
for ( Map.Entry<String, List<Mapping>> mapping : mappings.entrySet() ) {
TargetReference targetRef = first( mapping.getValue() ).getTargetReference();
List<PropertyEntry> propertyEntries = targetRef.getPropertyEntries();
for ( int i = 0; i < propertyEntries.size() - 1; i++ ) {
PropertyEntry entry = propertyEntries.get( i );
uniquePropertyEntries.put( entry.getFullName(), entry );
}
}
// assign variable names and create local variables.
for ( PropertyEntry propertyEntry : uniquePropertyEntries.values() ) {
String name = getSaveVariableName( propertyEntry.getName(), existingVariableNames );
existingVariableNames.add( name );
Type type = propertyEntry.getType();
Assignment factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, type, null );
localVariables.add( new LocalVariable( name, type, factoryMethod ) );
localVariableNames.put( propertyEntry.getFullName(), name );
}
Set<NestedLocalVariableAssignment> relations = new HashSet<NestedLocalVariableAssignment>();
// create relations branches (getter / setter) -- need to go to postInit()
for ( Map.Entry<String, List<Mapping>> mapping : mappings.entrySet() ) {
TargetReference targetRef = first( mapping.getValue() ).getTargetReference();
List<PropertyEntry> propertyEntries = targetRef.getPropertyEntries();
for ( int i = 0; i < propertyEntries.size() - 1; i++ ) {
// null means the targetBean is the methods targetBean. Needs to be set later.
String targetBean = null;
if ( i > 0 ) {
PropertyEntry targetPropertyEntry = propertyEntries.get( i - 1 );
targetBean = localVariableNames.get( targetPropertyEntry.getFullName() );
}
PropertyEntry sourcePropertyEntry = propertyEntries.get( i );
String targetAccessor = sourcePropertyEntry.getWriteAccessor().getSimpleName().toString();
String sourceRef = localVariableNames.get( sourcePropertyEntry.getFullName() );
relations.add( new NestedLocalVariableAssignment(
targetBean,
targetAccessor,
sourceRef,
sourcePropertyEntry.getWriteAccessor().getExecutable() == null
) );
}
}
return new NestedTargetObjects( localVariables, localVariableNames, relations );
}
}
private NestedTargetObjects(Set<LocalVariable> localVariables, Map<String, String> localVariableNames,
Set<NestedLocalVariableAssignment> relations) {
this.localVariables = localVariables;
this.localVariableNames = localVariableNames;
this.nestedAssignments = relations;
}
/**
* returns a local vaRriable name when relevant (so when not the 'parameter' targetBean should be used)
*
* @param targetRef
*
* @return generated local variable name
*/
private String getLocalVariableName(TargetReference targetRef) {
String result = null;
List<PropertyEntry> propertyEntries = targetRef.getPropertyEntries();
if ( propertyEntries.size() > 1 ) {
result = localVariableNames.get( propertyEntries.get( propertyEntries.size() - 2 ).getFullName() );
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( getResultType() == null ) ? 0 : getResultType().hashCode() );
return result;
}
private NestedTargetObjects init(String targetBeanName) {
for ( NestedLocalVariableAssignment nestedAssignment : nestedAssignments ) {
if ( nestedAssignment.getTargetBean() == null ) {
nestedAssignment.setTargetBean( targetBeanName );
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
}
return this;
if ( obj == null || getClass() != obj.getClass() ) {
return false;
}
BeanMappingMethod that = (BeanMappingMethod) obj;
if ( !super.equals( obj ) ) {
return false;
}
return propertyMappings != null ? propertyMappings.equals( that.propertyMappings ) :
that.propertyMappings == null;
}
private interface SingleMappingByTargetPropertyNameFunction {
Mapping getSingleMappingByTargetPropertyName(String targetPropertyName);
}

View File

@ -1,88 +0,0 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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.mapstruct.ap.internal.model;
import java.util.Set;
import org.mapstruct.ap.internal.model.assignment.Assignment;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.util.Collections;
/**
* Local variable used in a mapping method.
*
* @author Sjaak Derksen
*/
public class LocalVariable extends ModelElement {
private final String name;
private final Type type;
private final Assignment factoryMethod;
public LocalVariable( String name, Type type, Assignment factoryMethod ) {
this.name = name;
this.type = type;
this.factoryMethod = factoryMethod;
}
public String getName() {
return name;
}
public Type getType() {
return type;
}
public Assignment getFactoryMethod() {
return factoryMethod;
}
@Override
public String toString() {
return type.toString() + " " + name;
}
@Override
public Set<Type> getImportTypes() {
return Collections.asSet( type );
}
@Override
public int hashCode() {
int hash = 5;
hash = 23 * hash + (this.name != null ? this.name.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object obj) {
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
final LocalVariable other = (LocalVariable) obj;
if ( (this.name == null) ? (other.name != null) : !this.name.equals( other.name ) ) {
return false;
}
return true;
}
}

View File

@ -1,137 +0,0 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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.mapstruct.ap.internal.model;
import static java.util.Collections.emptySet;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Type;
/**
*
* @author Sjaak Derksen
*
* In the process of creating target mappings, MapStruct creates local variables.
* <pre>
* {@code Chart chart = new Chart();
*
* Label label = new Label();
* Artist artist = new Artist();
* Song song = new Song();
* Studio studio = new Studio();
* artist.setLabel( label ); // NestedLocalVariableAssignment
* song.setArtist( artist ); // NestedLocalVariableAssignment
* label.setStudio( studio );// NestedLocalVariableAssignment
* chart.setSong( song ); // NestedLocalVariableAssignment
* }
*</pre>
*/
public class NestedLocalVariableAssignment extends ModelElement {
private String targetBean;
private final String setterName;
private final String sourceRef;
private final boolean fieldAssignment;
public NestedLocalVariableAssignment(String targetBean, String setterName, String sourceRef,
boolean fieldAssignment) {
this.targetBean = targetBean;
this.setterName = setterName;
this.sourceRef = sourceRef;
this.fieldAssignment = fieldAssignment;
}
/**
*
* @return the targetBean on which the property setter with {@link setterName} is called
*/
public String getTargetBean() {
return targetBean;
}
/**
*
* @param targetBean the targetBean on which the property setter with {@link setterName} is called
*/
public void setTargetBean(String targetBean) {
this.targetBean = targetBean;
}
/**
*
* @return the name of the setter (target accessor for the property)
*/
public String getSetterName() {
return setterName;
}
/**
*
* @return source reference, to be used a argument in the setter.
*/
public String getSourceRef() {
return sourceRef;
}
@Override
public Set<Type> getImportTypes() {
return emptySet();
}
/**
* @return {@code true}if field assignment should be used, {@code false} otherwise
*/
public boolean isFieldAssignment() {
return fieldAssignment;
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + ( this.targetBean != null ? this.targetBean.hashCode() : 0 );
hash = 97 * hash + ( this.sourceRef != null ? this.sourceRef.hashCode() : 0 );
hash = 97 * hash + ( this.fieldAssignment ? 1 : 0 );
return hash;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
final NestedLocalVariableAssignment other = (NestedLocalVariableAssignment) obj;
if ( ( this.targetBean == null ) ? ( other.targetBean != null ) :
!this.targetBean.equals( other.targetBean ) ) {
return false;
}
if ( ( this.sourceRef == null ) ? ( other.sourceRef != null ) : !this.sourceRef.equals( other.sourceRef ) ) {
return false;
}
return this.fieldAssignment == other.fieldAssignment;
}
}

View File

@ -40,6 +40,7 @@ import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.ForgedMethod;
import org.mapstruct.ap.internal.model.source.ForgedMethodHistory;
import org.mapstruct.ap.internal.model.source.FormattingParameters;
import org.mapstruct.ap.internal.model.source.MappingOptions;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.PropertyEntry;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
@ -70,7 +71,6 @@ public class PropertyMapping extends ModelElement {
private final String sourceBeanName;
private final String targetWriteAccessorName;
private final ValueProvider targetReadAccessorProvider;
private final String localTargetVarName;
private final Type targetType;
private final Assignment assignment;
private final List<String> dependsOn;
@ -107,7 +107,6 @@ public class PropertyMapping extends ModelElement {
protected Accessor targetReadAccessor;
protected TargetWriteAccessorType targetReadAccessorType;
protected String targetPropertyName;
protected String localTargetVarName;
protected List<String> dependsOn;
protected Set<String> existingVariableNames;
@ -147,11 +146,6 @@ public class PropertyMapping extends ModelElement {
return (T) this;
}
public T localTargetVarName(String localTargetVarName) {
this.localTargetVarName = localTargetVarName;
return (T) this;
}
private Type determineTargetType() {
// This is a bean mapping method, so we know the result is a declared type
DeclaredType resultType = (DeclaredType) method.getResultType().getTypeMirror();
@ -198,6 +192,7 @@ public class PropertyMapping extends ModelElement {
private SourceRHS rightHandSide;
private FormattingParameters formattingParameters;
private SelectionParameters selectionParameters;
private MappingOptions forgeMethodWithMappingOptions;
PropertyMappingBuilder() {
super( PropertyMappingBuilder.class );
@ -223,6 +218,11 @@ public class PropertyMapping extends ModelElement {
return this;
}
public PropertyMappingBuilder forgeMethodWithMappingOptions(MappingOptions mappingOptions) {
this.forgeMethodWithMappingOptions = mappingOptions;
return this;
}
public PropertyMapping build() {
// handle source
this.rightHandSide = getSourceRHS( sourceReference );
@ -238,7 +238,10 @@ public class PropertyMapping extends ModelElement {
preferUpdateMethods = method.getMappingTargetParameter() != null;
}
Assignment assignment = ctx.getMappingResolver().getTargetAssignment(
// forge a method instead of resolving one when there are mapping options.
Assignment assignment = null;
if ( forgeMethodWithMappingOptions == null ) {
assignment = ctx.getMappingResolver().getTargetAssignment(
method,
targetType,
targetPropertyName,
@ -247,6 +250,7 @@ public class PropertyMapping extends ModelElement {
rightHandSide,
preferUpdateMethods
);
}
Type sourceType = rightHandSide.getSourceType();
// No mapping found. Try to forge a mapping
@ -296,7 +300,6 @@ public class PropertyMapping extends ModelElement {
targetWriteAccessor.getSimpleName().toString(),
ValueProvider.of( targetReadAccessor ),
targetType,
localTargetVarName,
assignment,
dependsOn,
getDefaultValueAssignment( assignment )
@ -604,16 +607,32 @@ public class PropertyMapping extends ModelElement {
String name = getName( sourceType, targetType );
name = Strings.getSaveVariableName( name, ctx.getNamesOfMappingsToGenerate() );
List<Parameter> parameters = new ArrayList( method.getContextParameters() );
Type returnType;
if ( method.isUpdateMethod() ) {
// there's only one case for forging a method with mapping options: nested target properties.
// they should always forge an update method
if ( method.isUpdateMethod() || forgeMethodWithMappingOptions != null ) {
parameters.add( Parameter.forForgedMappingTarget( targetType ) );
returnType = ctx.getTypeFactory().createVoidType();
}
else {
returnType = targetType;
}
ForgedMethod forgedMethod = new ForgedMethod(
ForgedMethod forgedMethod;
if ( forgeMethodWithMappingOptions != null ) {
forgedMethod = new ForgedMethod(
name,
sourceType,
returnType,
method.getMapperConfiguration(),
method.getExecutable(),
parameters,
forgeMethodWithMappingOptions
);
}
else {
forgedMethod = new ForgedMethod(
name,
sourceType,
returnType,
@ -622,7 +641,7 @@ public class PropertyMapping extends ModelElement {
parameters,
getForgedMethodHistory( sourceRHS )
);
}
return createForgedBeanAssignment( sourceRHS, forgedMethod );
}
@ -768,7 +787,6 @@ public class PropertyMapping extends ModelElement {
targetWriteAccessor.getSimpleName().toString(),
ValueProvider.of( targetReadAccessor ),
targetType,
localTargetVarName,
assignment,
dependsOn,
null
@ -833,7 +851,6 @@ public class PropertyMapping extends ModelElement {
targetWriteAccessor.getSimpleName().toString(),
ValueProvider.of( targetReadAccessor ),
targetType,
localTargetVarName,
assignment,
dependsOn,
null
@ -845,15 +862,15 @@ public class PropertyMapping extends ModelElement {
// Constructor for creating mappings of constant expressions.
private PropertyMapping(String name, String targetWriteAccessorName,
ValueProvider targetReadAccessorProvider,
Type targetType, String localTargetVarName, Assignment propertyAssignment,
Type targetType, Assignment propertyAssignment,
List<String> dependsOn, Assignment defaultValueAssignment ) {
this( name, null, targetWriteAccessorName, targetReadAccessorProvider,
targetType, localTargetVarName, propertyAssignment, dependsOn, defaultValueAssignment
targetType, propertyAssignment, dependsOn, defaultValueAssignment
);
}
private PropertyMapping(String name, String sourceBeanName, String targetWriteAccessorName,
ValueProvider targetReadAccessorProvider, Type targetType, String localTargetVarName,
ValueProvider targetReadAccessorProvider, Type targetType,
Assignment assignment,
List<String> dependsOn, Assignment defaultValueAssignment) {
this.name = name;
@ -861,7 +878,6 @@ public class PropertyMapping extends ModelElement {
this.targetWriteAccessorName = targetWriteAccessorName;
this.targetReadAccessorProvider = targetReadAccessorProvider;
this.targetType = targetType;
this.localTargetVarName = localTargetVarName;
this.assignment = assignment;
this.dependsOn = dependsOn != null ? dependsOn : Collections.<String>emptyList();
@ -891,10 +907,6 @@ public class PropertyMapping extends ModelElement {
return targetType;
}
public String getLocalTargetVarName() {
return localTargetVarName;
}
public Assignment getAssignment() {
return assignment;
}
@ -920,6 +932,36 @@ public class PropertyMapping extends ModelElement {
return dependsOn;
}
@Override
public int hashCode() {
int hash = 5;
hash = 67 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 67 * hash + (this.targetType != null ? this.targetType.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
final PropertyMapping other = (PropertyMapping) obj;
if ( (this.name == null) ? (other.name != null) : !this.name.equals( other.name ) ) {
return false;
}
if ( this.targetType != other.targetType && (this.targetType == null ||
!this.targetType.equals( other.targetType )) ) {
return false;
}
return true;
}
@Override
public String toString() {
return "PropertyMapping {"

View File

@ -48,6 +48,8 @@ public class ForgedMethod implements Method {
private final List<Parameter> sourceParameters;
private final List<Parameter> contextParameters;
private final Parameter mappingTargetParameter;
private final MappingOptions mappingOptions;
private boolean autoMapping;
/**
* Creates a new forged method with the given name.
@ -61,7 +63,43 @@ public class ForgedMethod implements Method {
*/
public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration,
ExecutableElement positionHintElement, List<Parameter> additionalParameters) {
this( name, sourceType, returnType, mapperConfiguration, positionHintElement, additionalParameters, null );
this( name, sourceType, returnType, mapperConfiguration, positionHintElement, additionalParameters, null,
false, MappingOptions.empty() );
}
/**
* Creates a new forged method with the given name with history
*
* @param name the (unique name) for this method
* @param sourceType the source type
* @param returnType the return type.
* @param mapperConfiguration the mapper configuration
* @param positionHintElement element used to for reference to the position in the source file.
* @param additionalParameters additional parameters to add to the forged method
* @param history a parent forged method if this is a forged method within a forged method
*/
public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration,
ExecutableElement positionHintElement, List<Parameter> additionalParameters, ForgedMethodHistory history) {
this( name, sourceType, returnType, mapperConfiguration, positionHintElement, additionalParameters, history,
true, MappingOptions.empty() );
}
/**
* Creates a new forged method with the given name with mapping options
*
* @param name the (unique name) for this method
* @param sourceType the source type
* @param returnType the return type.
* @param mapperConfiguration the mapper configuration
* @param positionHintElement element used to for reference to the position in the source file.
* @param additionalParameters additional parameters to add to the forged method
* @param mappingOptions with mapping options
*/
public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration,
ExecutableElement positionHintElement, List<Parameter> additionalParameters,
MappingOptions mappingOptions) {
this( name, sourceType, returnType, mapperConfiguration, positionHintElement, additionalParameters, null,
false, mappingOptions );
}
/**
@ -74,15 +112,17 @@ public class ForgedMethod implements Method {
* @param positionHintElement element used to for reference to the position in the source file.
* @param additionalParameters additional parameters to add to the forged method
* @param history a parent forged method if this is a forged method within a forged method
* @param mappingOptions the mapping options for this method
*/
public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration,
private ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration,
ExecutableElement positionHintElement, List<Parameter> additionalParameters,
ForgedMethodHistory history) {
ForgedMethodHistory history, boolean autoMapping, MappingOptions mappingOptions) {
String sourceParamName = Strings.decapitalize( sourceType.getName() );
String sourceParamSafeName = Strings.getSaveVariableName( sourceParamName );
this.parameters = new ArrayList<Parameter>( 1 + additionalParameters.size() );
this.parameters.add( new Parameter( sourceParamSafeName, sourceType ) );
Parameter sourceParameter = new Parameter( sourceParamSafeName, sourceType );
this.parameters.add( sourceParameter );
this.parameters.addAll( additionalParameters );
this.sourceParameters = Parameter.getSourceParameters( parameters );
this.contextParameters = Parameter.getContextParameters( parameters );
@ -94,6 +134,9 @@ public class ForgedMethod implements Method {
this.mapperConfiguration = mapperConfiguration;
this.positionHintElement = positionHintElement;
this.history = history;
this.autoMapping = autoMapping;
this.mappingOptions = mappingOptions;
this.mappingOptions.initWithParameter( sourceParameter );
}
/**
@ -108,10 +151,12 @@ public class ForgedMethod implements Method {
this.mapperConfiguration = forgedMethod.mapperConfiguration;
this.positionHintElement = forgedMethod.positionHintElement;
this.history = forgedMethod.history;
this.autoMapping = forgedMethod.autoMapping;
this.sourceParameters = Parameter.getSourceParameters( parameters );
this.contextParameters = Parameter.getContextParameters( parameters );
this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters );
this.mappingOptions = forgedMethod.mappingOptions;
this.name = name;
}
@ -195,6 +240,10 @@ public class ForgedMethod implements Method {
return history;
}
public boolean isAutoMapping() {
return autoMapping;
}
public void addThrownTypes(List<Type> thrownTypesToAdd) {
for ( Type thrownType : thrownTypesToAdd ) {
// make sure there are no duplicates coming from the keyAssignment thrown types.
@ -278,6 +327,11 @@ public class ForgedMethod implements Method {
return false;
}
@Override
public MappingOptions getMappingOptions() {
return mappingOptions;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
@ -306,5 +360,4 @@ public class ForgedMethod implements Method {
result = 31 * result + ( name != null ? name.hashCode() : 0 );
return result;
}
}

View File

@ -25,7 +25,6 @@ import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ElementKind;
@ -33,13 +32,14 @@ import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism;
import org.mapstruct.ap.internal.prism.MappingPrism;
import org.mapstruct.ap.internal.prism.MappingsPrism;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;
/**
* Represents a property mapping as configured via {@code @Mapping}.
@ -184,6 +184,24 @@ public class Mapping {
this.dependsOn = dependsOn;
}
private Mapping( Mapping mapping, TargetReference targetReference ) {
this.sourceName = mapping.sourceName;
this.constant = mapping.constant;
this.javaExpression = mapping.javaExpression;
this.targetName = Strings.join( targetReference.getElementNames(), "." );
this.defaultValue = mapping.defaultValue;
this.isIgnored = mapping.isIgnored;
this.mirror = mapping.mirror;
this.sourceAnnotationValue = mapping.sourceAnnotationValue;
this.targetAnnotationValue = mapping.targetAnnotationValue;
this.formattingParameters = mapping.formattingParameters;
this.selectionParameters = mapping.selectionParameters;
this.dependsOnAnnotationValue = mapping.dependsOnAnnotationValue;
this.dependsOn = mapping.dependsOn;
this.sourceReference = mapping.sourceReference;
this.targetReference = targetReference;
}
private static String getExpression(MappingPrism mappingPrism, ExecutableElement element,
FormattingMessager messager) {
if ( mappingPrism.expression().isEmpty() ) {
@ -228,6 +246,21 @@ public class Mapping {
}
}
/**
* Initializes the mapping with a new source parameter.
*
* @param sourceParameter
*/
public void init( Parameter sourceParameter ) {
if ( sourceReference != null ) {
SourceReference oldSourceReference = sourceReference;
sourceReference = new SourceReference.BuilderFromSourceReference()
.sourceParameter( sourceParameter )
.sourceReference( oldSourceReference )
.build();
}
}
/**
* Returns the complete source name of this mapping, either a qualified (e.g. {@code parameter1.foo}) or
* unqualified (e.g. {@code foo}) property reference.
@ -290,6 +323,16 @@ public class Mapping {
return targetReference;
}
public Mapping popTargetReference() {
if ( targetReference != null ) {
TargetReference newTargetReference = targetReference.pop();
if (newTargetReference != null ) {
return new Mapping(this, newTargetReference );
}
}
return null;
}
public List<String> getDependsOn() {
return dependsOn;
}

View File

@ -21,6 +21,7 @@ package org.mapstruct.ap.internal.model.source;
import static org.mapstruct.ap.internal.util.Collections.first;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -28,6 +29,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.util.FormattingMessager;
@ -43,14 +45,37 @@ public class MappingOptions {
private BeanMapping beanMapping;
private List<ValueMapping> valueMappings;
private boolean fullyInitialized;
private final boolean restrictToDefinedMappings;
public MappingOptions(Map<String, List<Mapping>> mappings, IterableMapping iterableMapping, MapMapping mapMapping,
BeanMapping beanMapping, List<ValueMapping> valueMappings ) {
BeanMapping beanMapping, List<ValueMapping> valueMappings, boolean restrictToDefinedMappings ) {
this.mappings = mappings;
this.iterableMapping = iterableMapping;
this.mapMapping = mapMapping;
this.beanMapping = beanMapping;
this.valueMappings = valueMappings;
this.restrictToDefinedMappings = restrictToDefinedMappings;
}
/**
* creates empty mapping options
*
* @return empty mapping options
*/
public static MappingOptions empty() {
return new MappingOptions( Collections.<String, List<Mapping>>emptyMap(), null, null, null,
Collections.<ValueMapping>emptyList(), false );
}
/**
* creates mapping options with only regular mappings
*
* @param mappings regular mappings to add
* @return MappingOptions with only regular mappings
*/
public static MappingOptions forMappingsOnly( Map<String, List<Mapping>> mappings ) {
return new MappingOptions( mappings, null, null, null, Collections.<ValueMapping>emptyList(), true );
}
/**
@ -61,6 +86,146 @@ public class MappingOptions {
return mappings;
}
/**
* The target references are popped. The MappingOptions are keyed on the unique first entries of the
* target references.
*
* So, take
*
* targetReference 1: propertyEntryX.propertyEntryX1.propertyEntryX1a
* targetReference 2: propertyEntryX.propertyEntryX2
* targetReference 3: propertyEntryY.propertyY1
* targetReference 4: propertyEntryZ
*
* will be popped and grouped into entries:
*
* propertyEntryX - MappingOptions ( targetReference1: propertyEntryX1.propertyEntryX1a,
* targetReference2: propertyEntryX2 )
* propertyEntryY - MappingOptions ( targetReference1: propertyEntryY1 )
*
* The key will be the former top level property, the MappingOptions will contain the remainders.
*
* So, 2 cloned new MappingOptions with popped targetReferences. Also Note that the not nested targetReference4
* disappeared.
*
* @return See above
*/
public Map<PropertyEntry, MappingOptions> groupByPoppedTargetReferences() {
// group all mappings based on the top level name before popping
Map<PropertyEntry, List<Mapping>> mappingsKeyedByProperty = new HashMap<PropertyEntry, List<Mapping>>();
for ( List<Mapping> mapping : mappings.values() ) {
Mapping newMapping = first( mapping ).popTargetReference();
if ( newMapping != null ) {
// group properties on current name.
PropertyEntry property = first( first( mapping ).getTargetReference().getPropertyEntries() );
if ( !mappingsKeyedByProperty.containsKey( property ) ) {
mappingsKeyedByProperty.put( property, new ArrayList<Mapping>() );
}
mappingsKeyedByProperty.get( property ).add( newMapping );
}
}
// now group them into mapping options
Map<PropertyEntry, MappingOptions> result = new HashMap<PropertyEntry, MappingOptions>();
for ( Map.Entry<PropertyEntry, List<Mapping>> mappingKeyedByProperty : mappingsKeyedByProperty.entrySet() ) {
Map<String, List<Mapping>> newEntries = new HashMap<String, List<Mapping>>();
for ( Mapping newEntry : mappingKeyedByProperty.getValue() ) {
newEntries.put( newEntry.getTargetName(), Arrays.asList( newEntry ) );
}
result.put( mappingKeyedByProperty.getKey(), forMappingsOnly( newEntries ) );
}
return result;
}
/**
* Check there are nested target references for this mapping options.
*
* @return boolean, true if there are nested target references
*/
public boolean hasNestedTargetReferences() {
for ( List<Mapping> mappingList : mappings.values() ) {
for ( Mapping mapping : mappingList ) {
TargetReference targetReference = mapping.getTargetReference();
if ( targetReference.isValid() && targetReference.getPropertyEntries().size() > 1 ) {
return true;
}
}
}
return false;
}
/**
*
* @return all dependencies to other properties the contained mappings are dependent on
*/
public List<String> collectNestedDependsOn() {
List<String> nestedDependsOn = new ArrayList<String>();
for ( List<Mapping> mappingList : mappings.values() ) {
for ( Mapping mapping : mappingList ) {
nestedDependsOn.addAll( mapping.getDependsOn() );
}
}
return nestedDependsOn;
}
/**
* Splits the MappingOptions into possibly more MappingOptions based on each source method parameter type.
*
* 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
*
* @return the split mapping options.
*
*/
public Map<Parameter, MappingOptions> groupBySourceParameter() {
Map<Parameter, List<Mapping>> mappingsKeyedByParameterType = new HashMap<Parameter, List<Mapping>>();
for ( List<Mapping> mappingList : mappings.values() ) {
for ( Mapping mapping : mappingList ) {
if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() ) {
Parameter parameter = mapping.getSourceReference().getParameter();
if ( !mappingsKeyedByParameterType.containsKey( parameter ) ) {
mappingsKeyedByParameterType.put( parameter, new ArrayList<Mapping>() );
}
mappingsKeyedByParameterType.get( parameter ).add( mapping );
}
}
}
Map<Parameter, MappingOptions> result = new HashMap<Parameter, MappingOptions>();
for ( Map.Entry<Parameter, List<Mapping>> entry : mappingsKeyedByParameterType.entrySet() ) {
result.put( entry.getKey(), MappingOptions.forMappingsOnly( groupByTargetName( entry.getValue() ) ) );
}
return result;
}
private Map<String, List<Mapping>> groupByTargetName( List<Mapping> mappingList ) {
Map<String, List<Mapping>> result = new HashMap<String, List<Mapping>>();
for ( Mapping mapping : mappingList ) {
if ( !result.containsKey( mapping.getTargetName() ) ) {
result.put( mapping.getTargetName(), new ArrayList<Mapping>() );
}
result.get( mapping.getTargetName() ).add( mapping );
}
return result;
}
/**
* Initializes the underlying mappings with a new property. Specifically used in in combination with forged methods
* where the new parameter name needs to be established at a later moment.
*
* @param sourceParameter the new source parameter
*/
public void initWithParameter( Parameter sourceParameter ) {
for ( List<Mapping> mappingList : mappings.values() ) {
for ( Mapping mapping : mappingList ) {
mapping.init( sourceParameter );
}
}
}
public IterableMapping getIterableMapping() {
return iterableMapping;
}
@ -223,4 +388,8 @@ public class MappingOptions {
}
public boolean isRestrictToDefinedMappings() {
return restrictToDefinedMappings;
}
}

View File

@ -189,4 +189,10 @@ public interface Method {
* {@code @MappingTarget}.
*/
boolean isUpdateMethod();
/**
*
* @return the mapping options for this method
*/
MappingOptions getMappingOptions();
}

View File

@ -29,14 +29,6 @@ import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
/**
* A PropertyEntry contains information on the name, readAccessor (for source), readAccessor and writeAccessor
* (for targets) and return type of a property.
*
* It can be shared between several nested properties. For example
*
* bean
*
* nestedMapping1 = "x.y1.z1" nestedMapping2 = "x.y1.z2" nestedMapping3 = "x.y2.z3"
*
* has property entries x, y1, y2, z1, z2, z3.
*/
public class PropertyEntry {
@ -115,4 +107,39 @@ public class PropertyEntry {
return Strings.join( Arrays.asList( fullName ), "." );
}
public PropertyEntry pop() {
if ( fullName.length > 1 ) {
String[] newFullName = Arrays.copyOfRange( fullName, 1, fullName.length );
return new PropertyEntry(newFullName, readAccessor, writeAccessor, presenceChecker, type );
}
else {
return null;
}
}
@Override
public int hashCode() {
int hash = 7;
hash = 23 * hash + Arrays.deepHashCode( this.fullName );
return hash;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
final PropertyEntry other = (PropertyEntry) obj;
if ( !Arrays.deepEquals( this.fullName, other.fullName ) ) {
return false;
}
return true;
}
}

View File

@ -185,7 +185,7 @@ public class SourceMethod implements Method {
public SourceMethod build() {
MappingOptions mappingOptions =
new MappingOptions( mappings, iterableMapping, mapMapping, beanMapping, valueMappings );
new MappingOptions( mappings, iterableMapping, mapMapping, beanMapping, valueMappings, false );
SourceMethod sourceMethod = new SourceMethod(
declaringMapper,

View File

@ -248,6 +248,29 @@ public class SourceReference {
}
}
/**
* Builds a {@link SourceReference} from a property.
*/
public static class BuilderFromSourceReference {
private Parameter sourceParameter;
private SourceReference sourceReference;
public BuilderFromSourceReference sourceReference(SourceReference sourceReference) {
this.sourceReference = sourceReference;
return this;
}
public BuilderFromSourceReference sourceParameter(Parameter sourceParameter) {
this.sourceParameter = sourceParameter;
return this;
}
public SourceReference build() {
return new SourceReference( sourceParameter, sourceReference.propertyEntries, true );
}
}
private SourceReference(Parameter sourceParameter, List<PropertyEntry> sourcePropertyEntries, boolean isValid) {
this.parameter = sourceParameter;
this.propertyEntries = sourcePropertyEntries;

View File

@ -202,14 +202,30 @@ public class TargetReference {
}
public List<String> getElementNames() {
List<String> elementName = new ArrayList<String>();
List<String> elementNames = new ArrayList<String>();
if ( parameter != null ) {
// only relevant for source properties
elementName.add( parameter.getName() );
elementNames.add( parameter.getName() );
}
for ( PropertyEntry propertyEntry : propertyEntries ) {
elementName.add( propertyEntry.getName() );
elementNames.add( propertyEntry.getName() );
}
return elementNames;
}
public TargetReference pop() {
if ( propertyEntries.size() > 1 ) {
List<PropertyEntry> newPropertyEntries = new ArrayList<PropertyEntry>( propertyEntries.size() - 1 );
for ( PropertyEntry propertyEntry : propertyEntries ) {
PropertyEntry newPropertyEntry = propertyEntry.pop();
if ( newPropertyEntry != null ) {
newPropertyEntries.add( newPropertyEntry );
}
}
return new TargetReference( null, newPropertyEntries, isValid );
}
else {
return null;
}
return elementName;
}
}

View File

@ -33,6 +33,7 @@ import org.mapstruct.ap.internal.model.common.Accessibility;
import org.mapstruct.ap.internal.model.common.ConversionContext;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.MappingOptions;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.util.MapperConfiguration;
import org.mapstruct.ap.internal.util.Strings;
@ -276,4 +277,9 @@ public abstract class BuiltInMethod implements Method {
public boolean isUpdateMethod() {
return getMappingTargetParameter() != null;
}
@Override
public MappingOptions getMappingOptions() {
return MappingOptions.empty();
}
}

View File

@ -42,7 +42,6 @@
</#if>
</#list>
<@nestedTargetObjects/>
<#if (sourceParameters?size > 1)>
<#list sourceParametersExcludingPrimitives as sourceParam>
<#if (propertyMappingsByParameter[sourceParam.name]?size > 0)>
@ -89,11 +88,3 @@
</#list>
</@compress>
</#macro>
<#macro nestedTargetObjects>
<#list localVariablesToCreate as localVariable>
<@includeModel object=localVariable/> = <#if localVariable.factoryMethod??><@includeModel object=localVariable.factoryMethod targetType=localVariable.type/><#else>new <@includeModel object=localVariable.type/>()</#if>;
</#list>
<#list nestedLocalVariableAssignments as nestedLocalVariableAssignment>
<@includeModel object=nestedLocalVariableAssignment/>
</#list>
</#macro>

View File

@ -1,21 +0,0 @@
<#--
Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
and/or other contributors as indicated by the @authors tag. See the
copyright.txt file in the distribution for a full listing of all
contributors.
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.
-->
<@includeModel object=type/> ${name}

View File

@ -1,22 +0,0 @@
<#--
Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
and/or other contributors as indicated by the @authors tag. See the
copyright.txt file in the distribution for a full listing of all
contributors.
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.
-->
<#import "macro/CommonMacros.ftl" as lib>
${targetBean}.${setterName}<@lib.handleWrite>${sourceRef}</@lib.handleWrite>;

View File

@ -18,15 +18,6 @@
limitations under the License.
-->
<#if localTargetVarName??>
<@includeModel object=assignment
targetBeanName=localTargetVarName
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=targetReadAccessorName
targetWriteAccessorName=targetWriteAccessorName
targetType=targetType
defaultValueAssignment=defaultValueAssignment />
<#else>
<@includeModel object=assignment
targetBeanName=ext.targetBeanName
existingInstanceMapping=ext.existingInstanceMapping
@ -34,4 +25,3 @@
targetWriteAccessorName=targetWriteAccessorName
targetType=targetType
defaultValueAssignment=defaultValueAssignment />
</#if>

View File

@ -18,8 +18,8 @@
*/
package org.mapstruct.ap.test.nestedtargetproperties;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.mapstruct.InheritInverseConfiguration;
@ -65,10 +65,10 @@ public abstract class ChartEntryToArtist {
protected List<Integer> mapPosition(Integer in) {
if ( in != null ) {
return Arrays.asList( in );
return new ArrayList<Integer>( Arrays.asList( in ) );
}
else {
return Collections.<Integer>emptyList();
return new ArrayList<Integer>();
}
}

View File

@ -0,0 +1,70 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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.mapstruct.ap.test.nestedtargetproperties;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Mappings;
import org.mapstruct.NullValueCheckStrategy;
import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry;
import org.mapstruct.ap.test.nestedsourceproperties.source.Chart;
import org.mapstruct.factory.Mappers;
/**
* @author Sjaak Derksen
*/
@Mapper( nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS )
public abstract class ChartEntryToArtistUpdate {
public static final ChartEntryToArtistUpdate MAPPER = Mappers.getMapper( ChartEntryToArtistUpdate.class );
@Mappings({
@Mapping(target = "type", ignore = true),
@Mapping(target = "name", source = "chartName"),
@Mapping(target = "song.title", source = "songTitle" ),
@Mapping(target = "song.artist.name", source = "artistName" ),
@Mapping(target = "song.artist.label.studio.name", source = "recordedAt"),
@Mapping(target = "song.artist.label.studio.city", source = "city" ),
@Mapping(target = "song.positions", source = "position" )
})
public abstract void map(ChartEntry chartEntry, @MappingTarget Chart chart );
protected List<Integer> mapPosition(Integer in) {
if ( in != null ) {
return Arrays.asList( in );
}
else {
return Collections.<Integer>emptyList();
}
}
protected Integer mapPosition(List<Integer> in) {
if ( in != null && !in.isEmpty() ) {
return in.get( 0 );
}
else {
return null;
}
}
}

View File

@ -0,0 +1,38 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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.mapstruct.ap.test.nestedtargetproperties;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ap.test.nestedtargetproperties._target.FishTankDto;
import org.mapstruct.ap.test.nestedtargetproperties.source.FishTank;
import org.mapstruct.factory.Mappers;
/**
*
* @author Sjaak Derksen
*/
@Mapper
public interface FishTankMapper {
FishTankMapper INSTANCE = Mappers.getMapper( FishTankMapper.class );
@Mapping(target = "fish.kind", source = "fish.type")
FishTankDto map( FishTank source );
}

View File

@ -27,6 +27,12 @@ import org.mapstruct.ap.test.nestedsourceproperties.source.Chart;
import org.mapstruct.ap.test.nestedsourceproperties.source.Label;
import org.mapstruct.ap.test.nestedsourceproperties.source.Song;
import org.mapstruct.ap.test.nestedsourceproperties.source.Studio;
import org.mapstruct.ap.test.nestedtargetproperties._target.FishDto;
import org.mapstruct.ap.test.nestedtargetproperties._target.FishTankDto;
import org.mapstruct.ap.test.nestedtargetproperties._target.WaterPlantDto;
import org.mapstruct.ap.test.nestedtargetproperties.source.Fish;
import org.mapstruct.ap.test.nestedtargetproperties.source.FishTank;
import org.mapstruct.ap.test.nestedtargetproperties.source.WaterPlant;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
@ -35,13 +41,28 @@ import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
*
* @author Sjaak Derksen
*/
@WithClasses({Song.class, Artist.class, Chart.class, Label.class, Studio.class, ChartEntry.class})
@WithClasses({
Song.class,
Artist.class,
Chart.class,
Label.class,
Studio.class,
ChartEntry.class,
FishDto.class,
FishTankDto.class,
WaterPlantDto.class,
Fish.class,
FishTank.class,
WaterPlant.class,
ChartEntryToArtist.class,
ChartEntryToArtistUpdate.class,
FishTankMapper.class
})
@IssueKey("389")
@RunWith(AnnotationProcessorTestRunner.class)
public class NestedTargetPropertiesTest {
@Test
@WithClasses({ChartEntryToArtist.class})
public void shouldMapNestedTarget() {
ChartEntry chartEntry = new ChartEntry();
@ -71,7 +92,6 @@ public class NestedTargetPropertiesTest {
}
@Test
@WithClasses({ChartEntryToArtist.class})
public void shouldMapNestedComposedTarget() {
ChartEntry chartEntry1 = new ChartEntry();
@ -103,7 +123,6 @@ public class NestedTargetPropertiesTest {
}
@Test
@WithClasses({ChartEntryToArtist.class})
public void shouldReverseNestedTarget() {
ChartEntry chartEntry = new ChartEntry();
@ -125,4 +144,64 @@ public class NestedTargetPropertiesTest {
assertThat( result.getRecordedAt() ).isEqualTo( "Live, First Avenue, Minneapolis" );
assertThat( result.getSongTitle() ).isEqualTo( "Purple Rain" );
}
@Test
public void shouldMapNestedTargetWitUpdate() {
ChartEntry chartEntry = new ChartEntry();
chartEntry.setArtistName( "Prince" );
chartEntry.setChartName( "US Billboard Hot Rock Songs" );
chartEntry.setCity( "Minneapolis" );
chartEntry.setPosition( 1 );
chartEntry.setRecordedAt( "Live, First Avenue, Minneapolis" );
chartEntry.setSongTitle( null );
Chart result = new Chart();
result.setSong( new Song() );
result.getSong().setTitle( "Raspberry Beret" );
ChartEntryToArtistUpdate.MAPPER.map( chartEntry, result );
assertThat( result.getName() ).isEqualTo( "US Billboard Hot Rock Songs" );
assertThat( result.getSong() ).isNotNull();
assertThat( result.getSong().getArtist() ).isNotNull();
assertThat( result.getSong().getTitle() ).isEqualTo( "Raspberry Beret" );
assertThat( result.getSong().getArtist().getName() ).isEqualTo( "Prince" );
assertThat( result.getSong().getArtist().getLabel() ).isNotNull();
assertThat( result.getSong().getArtist().getLabel().getStudio() ).isNotNull();
assertThat( result.getSong().getArtist().getLabel().getStudio().getName() )
.isEqualTo( "Live, First Avenue, Minneapolis" );
assertThat( result.getSong().getArtist().getLabel().getStudio().getCity() )
.isEqualTo( "Minneapolis" );
assertThat( result.getSong().getPositions() ).hasSize( 1 );
assertThat( result.getSong().getPositions().get( 0 ) ).isEqualTo( 1 );
}
@Test
public void automappingAndTargetNestingDemonstrator() {
FishTank source = new FishTank();
source.setName( "MyLittleFishTank" );
Fish fish = new Fish();
fish.setType( "Carp" );
WaterPlant waterplant = new WaterPlant();
waterplant.setKind( "Water Hyacinth" );
source.setFish( fish );
source.setPlant( waterplant );
FishTankDto target = FishTankMapper.INSTANCE.map( source );
// the nested property generates a method fishTankToFishDto(FishTank fishTank, FishDto mappingTarget)
// when name based mapping continues MapStruct searches for a property called `name` in fishTank (type
// 'FishTank'. If it is there, it should most cerntainly not be mapped to a mappingTarget of type 'FishDto'
assertThat( target.getFish() ).isNotNull();
assertThat( target.getFish().getKind() ).isEqualTo( "Carp" );
assertThat( target.getFish().getName() ).isNull();
// automapping takes care of mapping property "waterPlant".
assertThat( target.getPlant() ).isNotNull();
assertThat( target.getPlant().getKind() ).isEqualTo( "Water Hyacinth" );
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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.mapstruct.ap.test.nestedtargetproperties._target;
/**
*
* @author Sjaak Derksen
*/
public class FishDto {
private String kind;
// make sure that mapping on name does not happen based on name mapping
private String name;
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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.mapstruct.ap.test.nestedtargetproperties._target;
/**
*
* @author Sjaak Derksen
*/
public class FishTankDto {
private FishDto fish;
private WaterPlantDto plant;
public FishDto getFish() {
return fish;
}
public void setFish(FishDto fish) {
this.fish = fish;
}
public WaterPlantDto getPlant() {
return plant;
}
public void setPlant(WaterPlantDto plant) {
this.plant = plant;
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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.mapstruct.ap.test.nestedtargetproperties._target;
/**
*
* @author Sjaak Derksen
*/
public class WaterPlantDto {
private String kind;
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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.mapstruct.ap.test.nestedtargetproperties.source;
/**
*
* @author Sjaak Derksen
*/
public class Fish {
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@ -0,0 +1,55 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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.mapstruct.ap.test.nestedtargetproperties.source;
/**
*
* @author Sjaak Derksen
*/
public class FishTank {
private Fish fish;
private WaterPlant plant;
private String name;
public Fish getFish() {
return fish;
}
public void setFish(Fish fish) {
this.fish = fish;
}
public WaterPlant getPlant() {
return plant;
}
public void setPlant(WaterPlant plant) {
this.plant = plant;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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.mapstruct.ap.test.nestedtargetproperties.source;
/**
*
* @author Sjaak Derksen
*/
public class WaterPlant {
private String kind;
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
}