![JAR search and dependency download from the Maven repository](/logo.png)
net.projectmonkey.internal.PropertyMappingBuilder Maven / Gradle / Ivy
/*
* Copyright 2011 the original author or authors.
*
* 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 net.projectmonkey.internal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.projectmonkey.TypeMap;
import net.projectmonkey.config.Configuration;
import net.projectmonkey.internal.converter.ConverterStore;
import net.projectmonkey.internal.util.Iterables;
import net.projectmonkey.internal.util.Primitives;
import net.projectmonkey.internal.util.Strings;
import net.projectmonkey.spi.ConditionalConverter;
import net.projectmonkey.spi.Mapping;
import net.projectmonkey.spi.MatchingStrategy;
import net.projectmonkey.spi.PropertyInfo;
import net.projectmonkey.spi.ConditionalConverter.MatchResult;
/**
* Builds and populates implicit property mappings for a TypeMap.
*
* @param source type
* @param destination type
*
* @author Jonathan Halterman
*/
class PropertyMappingBuilder {
private final TypeMapImpl typeMap;
private final TypeInfo sourceTypeInfo;
private final TypeMapStore typeMapStore;
private final Configuration configuration;
private final ConverterStore typeConverterStore;
private final MatchingStrategy matchingStrategy;
/** Mutable state */
private final Errors errors = new Errors();
private final PropertyNameInfoImpl propertyNameInfo;
private final Set> sourceTypes = new HashSet>();
private final Set> destinationTypes = new HashSet>();
private final List mappings = new ArrayList();
/** Mappings for which the source accessor type was not verified by the supported converter */
private final List unverifiedMappings = new ArrayList();
private final PropertiesMatcher propertiesMatcher;
PropertyMappingBuilder(final TypeMapImpl typeMap, final TypeMapStore typeMapStore,
final ConverterStore converterStore) {
this.typeMap = typeMap;
this.typeConverterStore = converterStore;
this.typeMapStore = typeMapStore;
this.configuration = typeMap.configuration;
this.sourceTypeInfo = TypeInfoRegistry.typeInfoFor(typeMap.getSourceType(), configuration);
this.matchingStrategy = configuration.getMatchingStrategy();
this.propertyNameInfo = new PropertyNameInfoImpl(typeMap.getSourceType(), configuration);
this.propertiesMatcher = new PropertiesMatcher(configuration);
}
void build() {
matchDestination(TypeInfoRegistry.typeInfoFor(typeMap.getDestinationType(), configuration));
}
private void matchDestination(final TypeInfo> destinationTypeInfo) {
Class> destinationType = destinationTypeInfo.getType();
if (!Iterables.isIterable(destinationType) && !destinationTypes.add(destinationType))
throw errors.errorCircularReference(destinationType).toConfigurationException();
for (Map.Entry entry : destinationTypeInfo.getMutators().entrySet()) {
Mutator mutator = entry.getValue();
propertyNameInfo.pushDestination(entry.getKey(), entry.getValue());
// Skip explicit mappings
if (!typeMap.isMapped(Strings.join(propertyNameInfo.destinationProperties))) {
matchSource(sourceTypeInfo, mutator);
propertyNameInfo.clearSource();
sourceTypes.clear();
}
if (!unverifiedMappings.isEmpty() && mappings.isEmpty())
mappings.addAll(unverifiedMappings);
if (!mappings.isEmpty()) {
if (mappings.size() == 1) {
typeMap.addMapping(mappings.get(0));
} else {
MappingImpl mapping = disambiguateMappings();
if (mapping != null)
typeMap.addMapping(mapping);
else if (!configuration.isAmbiguityIgnored())
errors.ambiguousDestination(mutator, mappings);
}
mappings.clear();
unverifiedMappings.clear();
} else {
TypeMap, ?> destinationMap = typeMapStore.get(typeMap.getSourceType(), mutator.getType());
if (destinationMap == null) {
matchDestination(TypeInfoRegistry.typeInfoFor(mutator.getType(), configuration));
} else {
mergeMappings(destinationMap);
}
}
propertyNameInfo.popDestination();
}
destinationTypes.remove(destinationTypeInfo.getType());
errors.throwConfigurationExceptionIfErrorsExist();
}
/**
* Matches a source accessor hierarchy to the {@code destinationMutator}.
*/
private void matchSource(final TypeInfo> sourceTypeInfo, final Mutator destinationMutator) {
sourceTypes.add(sourceTypeInfo.getType());
for (Map.Entry entry : sourceTypeInfo.getAccessors().entrySet()) {
Accessor accessor = entry.getValue();
propertyNameInfo.pushSource(entry.getKey(), entry.getValue());
if (matchingStrategy.matches(propertyNameInfo)) {
for (ConditionalConverter, ?> converter : typeConverterStore.getConverters()) {
MatchResult matchResult = converter.match(accessor.getType(),
destinationMutator.getType());
if (!MatchResult.NONE.equals(matchResult)) {
PropertyMappingImpl mapping = new PropertyMappingImpl(
propertyNameInfo.sourceProperties, propertyNameInfo.destinationProperties);
if (MatchResult.FULL.equals(matchResult)) {
mappings.add(mapping);
if (matchingStrategy.isExact())
return;
} else
unverifiedMappings.add(mapping);
break;
}
}
}
if (isRecursivelyMatchable(accessor.getType()))
matchSource(TypeInfoRegistry.typeInfoFor(accessor.getType(), configuration),
destinationMutator);
propertyNameInfo.popSource();
}
sourceTypes.remove(sourceTypeInfo.getType());
}
/**
* Disambiguates the captured mappings by looking for the mapping with property tokens that most
* closely match the destination.
*
* Mappings with exact matches take priority over those which do not.
* Mappings with a completely matched path in the same order take priority over paths which just happen to match in some order.
* Otherwise - the mapping with the most matches wins.
*
* @return closest matching mapping, else {@code null} if one could not be determined
*/
MappingImpl disambiguateMappings() {
int bestScore = Integer.MIN_VALUE;
PropertyMappingImpl closestMatch = null;
boolean singleMatch = true;
for (PropertyMappingImpl mapping : mappings) {
int mappingScore = 0;
List extends PropertyInfo> sourceProperties = mapping.getSourceProperties();
List extends PropertyInfo> destinationProperties = mapping.getDestinationProperties();
List combinedStrengths = new ArrayList();
combinedStrengths.addAll(propertiesMatcher.compareProperties(sourceProperties, destinationProperties));
combinedStrengths.addAll(propertiesMatcher.compareProperties(destinationProperties, sourceProperties));
for (MatchStrength matchStrength : combinedStrengths)
{
mappingScore -= matchStrength.getPriority();
}
if(mappingScore > bestScore){
closestMatch = mapping;
bestScore = mappingScore;
singleMatch = true;
}else if(mappingScore == bestScore){
singleMatch = false;
}
}
if(singleMatch){
return closestMatch;
}else{
return null;
}
}
/**
* Merges mappings from an existing TypeMap into the type map under construction.
*/
private void mergeMappings(final TypeMap, ?> destinationMap) {
for (Mapping mapping : destinationMap.getMappings())
typeMap.addMapping(((MappingImpl) mapping)
.createMergedCopy(propertyNameInfo.destinationProperties));
}
boolean isRecursivelyMatchable(final Class> type) {
return !Primitives.isPrimitive(type) && !type.isArray() && type != String.class
&& !sourceTypes.contains(type);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy