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

org.mapstruct.ap.internal.model.ValueMappingMethod Maven / Gradle / Ivy

There is a newer version: 1.6.3
Show newest version
/*
 * Copyright MapStruct Authors.
 *
 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
 */
package org.mapstruct.ap.internal.model;

import static org.mapstruct.ap.internal.util.Collections.first;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;

import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.source.ForgedMethod;
import org.mapstruct.ap.internal.model.source.ForgedMethodHistory;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.ValueMapping;
import org.mapstruct.ap.internal.prism.BeanMappingPrism;
import org.mapstruct.ap.internal.prism.MappingConstantsPrism;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;

/**
 * A {@link ValueMappingMethod} which maps one value type to another, optionally configured by one or more
 * {@link ValueMapping}s. For now, only enum-to-enum mapping is supported.
 *
 * @author Sjaak Derksen
 */
public class ValueMappingMethod extends MappingMethod {

    private final List valueMappings;
    private final String defaultTarget;
    private final String nullTarget;
    private final boolean throwIllegalArgumentException;
    private final boolean overridden;

    public static class Builder {

        private Method method;
        private MappingBuilderContext ctx;
        private final List trueValueMappings = new ArrayList<>();
        private ValueMapping defaultTargetValue = null;
        private ValueMapping nullTargetValue = null;
        private boolean applyNamebasedMappings = true;

        public Builder mappingContext(MappingBuilderContext mappingContext) {
            this.ctx = mappingContext;
            return this;
        }

        public Builder method(Method sourceMethod) {
            this.method = sourceMethod;
            return this;
        }

        public Builder valueMappings(List valueMappings) {
            for ( ValueMapping valueMapping : valueMappings ) {
                if ( MappingConstantsPrism.ANY_REMAINING.equals( valueMapping.getSource() ) ) {
                    defaultTargetValue = valueMapping;
                }
                else if ( MappingConstantsPrism.ANY_UNMAPPED.equals( valueMapping.getSource() ) ) {
                    defaultTargetValue = valueMapping;
                    applyNamebasedMappings = false;
                }
                else if ( MappingConstantsPrism.NULL.equals( valueMapping.getSource() ) ) {
                    nullTargetValue = valueMapping;
                }
                else {
                    trueValueMappings.add( valueMapping );
                }
            }
            return this;
        }

        public ValueMappingMethod build( ) {

            // initialize all relevant parameters
            List mappingEntries = new ArrayList<>();
            String nullTarget = null;
            String defaultTarget = null;
            boolean throwIllegalArgumentException = false;

            // for now, we're only dealing with enum mappings, populate relevant parameters based on enum-2-enum
            if ( first( method.getSourceParameters() ).getType().isEnumType() && method.getResultType().isEnumType() ) {
                mappingEntries.addAll( enumToEnumMapping( method ) );

                if ( (nullTargetValue != null) && !MappingConstantsPrism.NULL.equals( nullTargetValue.getTarget() ) ) {
                    // absense nulltargetvalue reverts to null. Or it could be a deliberate choice to return null
                    nullTarget = nullTargetValue.getTarget();
                }
                if ( defaultTargetValue != null ) {
                    // If the default target value is NULL then we should map it to null
                    defaultTarget = MappingConstantsPrism.NULL.equals( defaultTargetValue.getTarget() ) ? null :
                        defaultTargetValue.getTarget();
                }
                else {
                    throwIllegalArgumentException = true;
                }

            }

            // do before / after lifecycle mappings
            SelectionParameters selectionParameters = getSelectionParameters( method, ctx.getTypeUtils() );
            Set existingVariables = new HashSet<>( method.getParameterNames() );
            List beforeMappingMethods =
                LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables );
            List afterMappingMethods =
                LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables );

            // finally return a mapping
            return new ValueMappingMethod( method, mappingEntries, nullTarget, defaultTarget,
                throwIllegalArgumentException, beforeMappingMethods, afterMappingMethods );
        }

        private List enumToEnumMapping(Method method) {

            List mappings = new ArrayList<>();
            List unmappedSourceConstants
                = new ArrayList<>( first( method.getSourceParameters() ).getType().getEnumConstants() );


            if ( !reportErrorIfMappedEnumConstantsDontExist( method ) ) {
                return mappings;
            }

            // Start to fill the mappings with the defined valuemappings
            for ( ValueMapping valueMapping : trueValueMappings ) {
                String target =
                    MappingConstantsPrism.NULL.equals( valueMapping.getTarget() ) ? null : valueMapping.getTarget();
                mappings.add( new MappingEntry( valueMapping.getSource(), target ) );
                unmappedSourceConstants.remove( valueMapping.getSource() );
            }


            // add mappings based on name
            if ( applyNamebasedMappings ) {

                // get all target constants
                List targetConstants = method.getReturnType().getEnumConstants();
                for ( String sourceConstant : new ArrayList<>( unmappedSourceConstants ) ) {
                    if ( targetConstants.contains( sourceConstant ) ) {
                        mappings.add( new MappingEntry( sourceConstant, sourceConstant ) );
                        unmappedSourceConstants.remove( sourceConstant );
                    }
                }

                if ( defaultTargetValue == null && !unmappedSourceConstants.isEmpty() ) {
                    String sourceErrorMessage = "source";
                    String targetErrorMessage = "target";
                    if ( method instanceof ForgedMethod && ( (ForgedMethod) method ).getHistory() != null ) {
                        ForgedMethodHistory history = ( (ForgedMethod) method ).getHistory();
                        sourceErrorMessage = history.createSourcePropertyErrorMessage();
                        targetErrorMessage =
                            "\"" + history.getTargetType().toString() + " " + history.createTargetPropertyName() + "\"";
                    }
                    // all sources should now be matched, there's no default to fall back to, so if sources remain,
                    // we have an issue.
                    ctx.getMessager().printMessage( method.getExecutable(),
                        Message.VALUE_MAPPING_UNMAPPED_SOURCES,
                        sourceErrorMessage,
                        targetErrorMessage,
                        Strings.join( unmappedSourceConstants, ", " )
                    );

                }
            }
            return mappings;
        }

        private SelectionParameters getSelectionParameters(Method method, Types typeUtils) {
            BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() );
            if ( beanMappingPrism != null ) {
                List qualifiers = beanMappingPrism.qualifiedBy();
                List qualifyingNames = beanMappingPrism.qualifiedByName();
                TypeMirror resultType = beanMappingPrism.resultType();
                return new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils );
            }
            return null;
        }

        private boolean reportErrorIfMappedEnumConstantsDontExist(Method method) {
            List sourceEnumConstants = first( method.getSourceParameters() ).getType().getEnumConstants();
            List targetEnumConstants = method.getReturnType().getEnumConstants();

            boolean foundIncorrectMapping = false;

            for ( ValueMapping mappedConstant : trueValueMappings ) {

                if ( !sourceEnumConstants.contains( mappedConstant.getSource() ) ) {
                    ctx.getMessager().printMessage( method.getExecutable(),
                        mappedConstant.getMirror(),
                        mappedConstant.getSourceAnnotationValue(),
                        Message.VALUEMAPPING_NON_EXISTING_CONSTANT,
                        mappedConstant.getSource(),
                        first( method.getSourceParameters() ).getType()
                    );
                    foundIncorrectMapping = true;
                }
                if ( !MappingConstantsPrism.NULL.equals( mappedConstant.getTarget() )
                    && !targetEnumConstants.contains( mappedConstant.getTarget() ) ) {
                    ctx.getMessager().printMessage( method.getExecutable(),
                        mappedConstant.getMirror(),
                        mappedConstant.getTargetAnnotationValue(),
                        Message.VALUEMAPPING_NON_EXISTING_CONSTANT,
                        mappedConstant.getTarget(),
                        method.getReturnType()
                    );
                    foundIncorrectMapping = true;
                }
            }

            if ( defaultTargetValue != null && !MappingConstantsPrism.NULL.equals( defaultTargetValue.getTarget() )
                && !targetEnumConstants.contains( defaultTargetValue.getTarget() ) ) {
                ctx.getMessager().printMessage( method.getExecutable(),
                    defaultTargetValue.getMirror(),
                    defaultTargetValue.getTargetAnnotationValue(),
                    Message.VALUEMAPPING_NON_EXISTING_CONSTANT,
                    defaultTargetValue.getTarget(),
                    method.getReturnType()
                );
                foundIncorrectMapping = true;
            }

            if ( nullTargetValue != null && MappingConstantsPrism.NULL.equals( nullTargetValue.getTarget() )
                && !targetEnumConstants.contains( nullTargetValue.getTarget() ) ) {
                ctx.getMessager().printMessage( method.getExecutable(),
                    nullTargetValue.getMirror(),
                    nullTargetValue.getTargetAnnotationValue(),
                    Message.VALUEMAPPING_NON_EXISTING_CONSTANT,
                    nullTargetValue.getTarget(),
                    method.getReturnType()
                );
                foundIncorrectMapping = true;
            }

            return !foundIncorrectMapping;
        }
    }

    private ValueMappingMethod(Method method, List enumMappings, String nullTarget, String defaultTarget,
        boolean throwIllegalArgumentException, List beforeMappingMethods,
        List afterMappingMethods) {
        super( method, beforeMappingMethods, afterMappingMethods );
        this.valueMappings = enumMappings;
        this.nullTarget = nullTarget;
        this.defaultTarget = defaultTarget;
        this.throwIllegalArgumentException = throwIllegalArgumentException;
        this.overridden = method.overridesMethod();
    }

    public List getValueMappings() {
        return valueMappings;
    }

    public String getDefaultTarget() {
        return defaultTarget;
    }

    public String getNullTarget() {
        return nullTarget;
    }

    public boolean isThrowIllegalArgumentException() {
        return throwIllegalArgumentException;
    }

    public Parameter getSourceParameter() {
        return first( getSourceParameters() );
    }

    public boolean isOverridden() {
        return overridden;
    }

    public static class MappingEntry {
        private final String source;
        private final String target;

        MappingEntry( String source, String target ) {
            this.source = source;
            this.target = target;
        }

        public String getSource() {
            return source;
        }

        public String getTarget() {
            return target;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy