/**
* 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.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mapstruct.ap.internal.model.assignment.Assignment;
import org.mapstruct.ap.internal.model.assignment.LocalVarWrapper;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.ForgedMethod;
import org.mapstruct.ap.internal.model.common.FormattingParameters;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism;
import org.mapstruct.ap.internal.util.Strings;
import static org.mapstruct.ap.internal.util.Collections.first;
/**
* A {@link MappingMethod} implemented by a {@link Mapper} class which maps one {@code Map} type to another. Keys and
* values are mapped either by a {@link TypeConversion} or another mapping method if required.
*
* @author Gunnar Morling
*/
public class MapMappingMethod extends NormalTypeMappingMethod {
private final Assignment keyAssignment;
private final Assignment valueAssignment;
private IterableCreation iterableCreation;
public static class Builder extends AbstractMappingMethodBuilder<Builder, MapMappingMethod> {
private FormattingParameters keyFormattingParameters;
private FormattingParameters valueFormattingParameters;
private NullValueMappingStrategyPrism nullValueMappingStrategy;
private SelectionParameters keySelectionParameters;
private SelectionParameters valueSelectionParameters;
public Builder() {
super( Builder.class );
}
public Builder keySelectionParameters(SelectionParameters keySelectionParameters) {
this.keySelectionParameters = keySelectionParameters;
return this;
}
public Builder valueSelectionParameters(SelectionParameters valueSelectionParameters) {
this.valueSelectionParameters = valueSelectionParameters;
return this;
}
public Builder keyFormattingParameters(FormattingParameters keyFormattingParameters) {
this.keyFormattingParameters = keyFormattingParameters;
return this;
}
public Builder valueFormattingParameters(FormattingParameters valueFormattingParameters) {
this.valueFormattingParameters = valueFormattingParameters;
return this;
}
public Builder nullValueMappingStrategy(NullValueMappingStrategyPrism nullValueMappingStrategy) {
this.nullValueMappingStrategy = nullValueMappingStrategy;
return this;
}
public MapMappingMethod build() {
List<Type> sourceTypeParams =
first( method.getSourceParameters() ).getType().determineTypeArguments( Map.class );
List<Type> resultTypeParams = method.getResultType().determineTypeArguments( Map.class );
// find mapping method or conversion for key
Type keySourceType = sourceTypeParams.get( 0 ).getTypeBound();
Type keyTargetType = resultTypeParams.get( 0 ).getTypeBound();
SourceRHS keySourceRHS = new SourceRHS( "entry.getKey()", keySourceType, new HashSet<String>(), "map key" );
Assignment keyAssignment = ctx.getMappingResolver().getTargetAssignment(
method,
keyTargetType,
null, // there is no targetPropertyName
keyFormattingParameters,
keySelectionParameters,
keySourceRHS,
false
);
if ( keyAssignment == null ) {
keyAssignment = forgeMapping( keySourceRHS, keySourceType, keyTargetType );
}
if ( keyAssignment == null ) {
if ( method instanceof ForgedMethod ) {
// leave messaging to calling property mapping
return null;
}
else {
reportCannotCreateMapping(
method,
String.format(
"%s \"%s\"",
keySourceRHS.getSourceErrorMessagePart(),
keySourceRHS.getSourceType()
),
keySourceRHS,
keyTargetType,
""
);
}
}
// find mapping method or conversion for value
Type valueSourceType = sourceTypeParams.get( 1 ).getTypeBound();
Type valueTargetType = resultTypeParams.get( 1 ).getTypeBound();
SourceRHS valueSourceRHS = new SourceRHS( "entry.getValue()", valueSourceType, new HashSet<String>(),
"map value" );
Assignment valueAssignment = ctx.getMappingResolver().getTargetAssignment(
method,
valueTargetType,
null, // there is no targetPropertyName
valueFormattingParameters,
valueSelectionParameters,
valueSourceRHS,
false
);
if ( method instanceof ForgedMethod ) {
ForgedMethod forgedMethod = (ForgedMethod) method;
if ( keyAssignment != null ) {
forgedMethod.addThrownTypes( keyAssignment.getThrownTypes() );
}
if ( valueAssignment != null ) {
forgedMethod.addThrownTypes( valueAssignment.getThrownTypes() );
}
}
if ( valueAssignment == null ) {
valueAssignment = forgeMapping( valueSourceRHS, valueSourceType, valueTargetType );
}
if ( valueAssignment == null ) {
if ( method instanceof ForgedMethod ) {
// leave messaging to calling property mapping
return null;
}
else {
reportCannotCreateMapping(
method,
String.format(
"%s \"%s\"",
valueSourceRHS.getSourceErrorMessagePart(),
valueSourceRHS.getSourceType()
),
valueSourceRHS,
valueTargetType,
""
);
}
}
// mapNullToDefault
boolean mapNullToDefault = false;
if ( method.getMapperConfiguration() != null ) {
mapNullToDefault = method.getMapperConfiguration().isMapToDefault( nullValueMappingStrategy );
}
MethodReference factoryMethod = null;
if ( !method.isUpdateMethod() ) {
factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType(), null );
}
keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes(), keyTargetType, false );
valueAssignment = new LocalVarWrapper( valueAssignment, method.getThrownTypes(), valueTargetType, false );
Set<String> existingVariables = new HashSet<String>( method.getParameterNames() );
List<LifecycleCallbackMethodReference> beforeMappingMethods =
LifecycleCallbackFactory.beforeMappingMethods( method, null, ctx, existingVariables );
List<LifecycleCallbackMethodReference> afterMappingMethods =
LifecycleCallbackFactory.afterMappingMethods( method, null, ctx, existingVariables );
return new MapMappingMethod(
method,
existingVariables,
keyAssignment,
valueAssignment,
factoryMethod,
mapNullToDefault,
beforeMappingMethods,
afterMappingMethods
);
}
@Override
protected boolean shouldUsePropertyNamesInHistory() {
return true;
}
}
private MapMappingMethod(Method method, Collection<String> existingVariableNames, Assignment keyAssignment,
Assignment valueAssignment, MethodReference factoryMethod, boolean mapNullToDefault,
List<LifecycleCallbackMethodReference> beforeMappingReferences,
List<LifecycleCallbackMethodReference> afterMappingReferences) {
super( method, existingVariableNames, factoryMethod, mapNullToDefault, beforeMappingReferences,
afterMappingReferences );
this.keyAssignment = keyAssignment;
this.valueAssignment = valueAssignment;
}
public Parameter getSourceParameter() {
for ( Parameter parameter : getParameters() ) {
if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) {
return parameter;
}
}
throw new IllegalStateException( "Method " + this + " has no source parameter." );
}
public List<Type> getSourceElementTypes() {
Type sourceParameterType = getSourceParameter().getType();
return sourceParameterType.determineTypeArguments( Map.class );
}
public List<Type> getResultElementTypes() {
return getResultType().determineTypeArguments( Map.class );
}
public Assignment getKeyAssignment() {
return keyAssignment;
}
public Assignment getValueAssignment() {
return valueAssignment;
}
@Override
public Set<Type> getImportTypes() {
Set<Type> types = super.getImportTypes();
if ( keyAssignment != null ) {
types.addAll( keyAssignment.getImportTypes() );
}
if ( valueAssignment != null ) {
types.addAll( valueAssignment.getImportTypes() );
}
if ( iterableCreation != null ) {
types.addAll( iterableCreation.getImportTypes() );
}
return types;
}
public String getKeyVariableName() {
return Strings.getSaveVariableName(
"key",
getParameterNames()
);
}
public String getValueVariableName() {
return Strings.getSaveVariableName(
"value",
getParameterNames()
);
}
public String getEntryVariableName() {
return Strings.getSaveVariableName(
"entry",
getParameterNames()
);
}
public IterableCreation getIterableCreation() {
if ( iterableCreation == null ) {
iterableCreation = IterableCreation.create( this, getSourceParameter() );
}
return iterableCreation;
}
}