/** * 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.source; import static org.mapstruct.ap.internal.util.Collections.first; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; 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; /** * Encapsulates all options specifiable on a mapping method * * @author Andreas Gudian */ public class MappingOptions { private static final MappingOptions EMPTY = new MappingOptions( Collections.<String, List<Mapping>>emptyMap(), null, null, null, Collections.<ValueMapping>emptyList(), false ); private Map<String, List<Mapping>> mappings; private IterableMapping iterableMapping; private MapMapping mapMapping; 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, 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 EMPTY; } /** * creates mapping options with only regular mappings * * @param mappings regular mappings to add * @param restrictToDefinedMappings whether to restrict the mappings only to the defined mappings * @return MappingOptions with only regular mappings */ public static MappingOptions forMappingsOnly(Map<String, List<Mapping>> mappings, boolean restrictToDefinedMappings) { return new MappingOptions( mappings, null, null, restrictToDefinedMappings ? BeanMapping.forForgedMethods() : null, Collections.<ValueMapping>emptyList(), restrictToDefinedMappings ); } /** * @return the {@link Mapping}s configured for this method, keyed by target property name. Only for enum mapping * methods a target will be mapped by several sources. TODO. Remove the value list when 2.0 */ public Map<String, List<Mapping>> getMappings() { return mappings; } /** * 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; } /** * 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; } public MapMapping getMapMapping() { return mapMapping; } public BeanMapping getBeanMapping() { return beanMapping; } public List<ValueMapping> getValueMappings() { return valueMappings; } public void setMappings(Map<String, List<Mapping>> mappings) { this.mappings = mappings; } public void setIterableMapping(IterableMapping iterableMapping) { this.iterableMapping = iterableMapping; } public void setMapMapping(MapMapping mapMapping) { this.mapMapping = mapMapping; } public void setBeanMapping(BeanMapping beanMapping) { this.beanMapping = beanMapping; } public void setValueMappings(List<ValueMapping> valueMappings) { this.valueMappings = valueMappings; } /** * @return the {@code true}, iff the options have been fully initialized by applying all available inheritance * options */ public boolean isFullyInitialized() { return fullyInitialized; } public void markAsFullyInitialized() { this.fullyInitialized = true; } /** * Merges in all the mapping options configured, giving the already defined options precedence. * * @param inherited the options to inherit, may be {@code null} * @param isInverse if {@code true}, the specified options are from an inverse method * @param method the source method * @param messager the messager * @param typeFactory the type factory */ public void applyInheritedOptions(MappingOptions inherited, boolean isInverse, SourceMethod method, FormattingMessager messager, TypeFactory typeFactory) { if ( null != inherited ) { if ( getIterableMapping() == null ) { if ( inherited.getIterableMapping() != null ) { setIterableMapping( inherited.getIterableMapping() ); } } if ( getMapMapping() == null ) { if ( inherited.getMapMapping() != null ) { setMapMapping( inherited.getMapMapping() ); } } if ( getBeanMapping() == null ) { if ( inherited.getBeanMapping() != null ) { setBeanMapping( inherited.getBeanMapping() ); } } if ( getValueMappings() == null ) { if ( inherited.getValueMappings() != null ) { // there were no mappings, so the inherited mappings are the new ones setValueMappings( inherited.getValueMappings() ); } else { setValueMappings( Collections.<ValueMapping>emptyList() ); } } else { if ( inherited.getValueMappings() != null ) { // iff there are also inherited mappings, we reverse and add them. for ( ValueMapping inheritedValueMapping : inherited.getValueMappings() ) { ValueMapping valueMapping = isInverse ? inheritedValueMapping.reverse() : inheritedValueMapping; if ( valueMapping != null && !getValueMappings().contains( valueMapping ) ) { getValueMappings().add( valueMapping ); } } } } Map<String, List<Mapping>> newMappings = new HashMap<String, List<Mapping>>(); for ( List<Mapping> lmappings : inherited.getMappings().values() ) { for ( Mapping mapping : lmappings ) { if ( isInverse ) { mapping = mapping.reverse( method, messager, typeFactory ); } if ( mapping != null ) { List<Mapping> mappingsOfProperty = newMappings.get( mapping.getTargetName() ); if ( mappingsOfProperty == null ) { mappingsOfProperty = new ArrayList<Mapping>(); newMappings.put( mapping.getTargetName(), mappingsOfProperty ); } mappingsOfProperty.add( mapping.copyForInheritanceTo( method ) ); } } } // now add all of its own mappings newMappings.putAll( getMappings() ); // filter new mappings filterNestedTargetIgnores( newMappings ); setMappings( newMappings ); } markAsFullyInitialized(); } private void filterNestedTargetIgnores( Map<String, List<Mapping>> mappings) { // collect all properties to ignore, and safe their target name ( == same name as first ref target property) Set<String> ignored = new HashSet<String>(); for ( Map.Entry<String, List<Mapping>> mappingEntry : mappings.entrySet() ) { Mapping mapping = first( mappingEntry.getValue() ); // list only used for deprecated enums mapping if ( mapping.isIgnored() && mapping.getTargetReference().isValid() ) { ignored.add( mapping.getTargetName() ); } } // collect all entries to remove (avoid concurrent modification) Set<String> toBeRemoved = new HashSet<String>(); for ( Map.Entry<String, List<Mapping>> mappingEntry : mappings.entrySet() ) { Mapping mapping = first( mappingEntry.getValue() ); // list only used for deprecated enums mapping TargetReference targetReference = mapping.getTargetReference(); if ( targetReference.isValid() && targetReference.getPropertyEntries().size() > 1 && ignored.contains( first( targetReference.getPropertyEntries() ).getName() ) ) { toBeRemoved.add( mappingEntry.getKey() ); } } // finall remove all duplicates mappings.keySet().removeAll( toBeRemoved ); } public boolean isRestrictToDefinedMappings() { return restrictToDefinedMappings; } }