Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
ma.glasnost.orika.impl.generator.SourceCodeContext Maven / Gradle / Ivy
/*
* Orika - simpler, better and faster Java bean mapping
*
* Copyright (C) 2011-2013 Orika 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 ma.glasnost.orika.impl.generator;
import ma.glasnost.orika.*;
import ma.glasnost.orika.Properties;
import ma.glasnost.orika.converter.ConverterFactory;
import ma.glasnost.orika.impl.AggregateFilter;
import ma.glasnost.orika.impl.GeneratedObjectBase;
import ma.glasnost.orika.impl.generator.CompilerStrategy.SourceCodeGenerationException;
import ma.glasnost.orika.impl.generator.Node.NodeList;
import ma.glasnost.orika.impl.generator.UsedMapperFacadesContext.UsedMapperFacadesIndex;
import ma.glasnost.orika.impl.generator.specification.AbstractSpecification;
import ma.glasnost.orika.impl.util.ClassUtil;
import ma.glasnost.orika.metadata.*;
import ma.glasnost.orika.property.PropertyResolverStrategy;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import static java.lang.String.format;
import static ma.glasnost.orika.impl.Specifications.aMultiOccurrenceElementMap;
/**
* SourceCodeContext contains the state information necessary while generating
* source code for a given mapping object; it also houses various utility
* methods which can be used to aid in code generation.
*
*/
public class SourceCodeContext {
private static final AtomicInteger UNIQUE_CLASS_INDEX = new AtomicInteger();
private StringBuilder sourceBuilder;
private String classSimpleName;
private String packageName;
private String className;
private CompilerStrategy compilerStrategy;
private List methods;
private List fields;
private Class superClass;
private final UsedTypesContext usedTypes;
private final UsedConvertersContext usedConverters;
private final UsedFiltersContext usedFilters;
private final UsedMapperFacadesContext usedMapperFacades;
private final MapperFactory mapperFactory;
private final CodeGenerationStrategy codeGenerationStrategy;
private final StringBuilder logDetails;
private final PropertyResolverStrategy propertyResolver;
private final Map> aggregateFieldMaps;
private final MappingContext mappingContext;
private final Collection> filters;
private final boolean shouldCaptureFieldContext;
/**
* Constructs a new instance of SourceCodeContext
*
* @param baseClassName
* @param superClass
* @param mappingContext
* @param logDetails
*/
@SuppressWarnings("unchecked")
public SourceCodeContext(final String baseClassName, Class superClass, MappingContext mappingContext, StringBuilder logDetails) {
this.mapperFactory = (MapperFactory) mappingContext.getProperty(Properties.MAPPER_FACTORY);
this.codeGenerationStrategy = (CodeGenerationStrategy) mappingContext.getProperty(Properties.CODE_GENERATION_STRATEGY);
this.compilerStrategy = (CompilerStrategy) mappingContext.getProperty(Properties.COMPILER_STRATEGY);
this.propertyResolver = (PropertyResolverStrategy) mappingContext.getProperty(Properties.PROPERTY_RESOLVER_STRATEGY);
this.filters = (Collection>) mappingContext.getProperty(Properties.FILTERS);
this.shouldCaptureFieldContext = (Boolean) mappingContext.getProperty(Properties.CAPTURE_FIELD_CONTEXT);
String safeBaseClassName = baseClassName.replace("[]", "$Array");
this.sourceBuilder = new StringBuilder();
this.superClass = superClass;
int namePos = safeBaseClassName.lastIndexOf(".");
if (namePos > 0) {
this.packageName = safeBaseClassName.substring(0, namePos);
this.classSimpleName = safeBaseClassName.substring(namePos + 1);
} else {
this.packageName = "ma.glasnost.orika.generated";
this.classSimpleName = safeBaseClassName;
}
this.classSimpleName = makeUniqueClassName(this.classSimpleName);
this.className = this.packageName + "." + this.classSimpleName;
this.methods = new ArrayList();
this.fields = new ArrayList();
sourceBuilder.append("package " + packageName + ";\n\n");
sourceBuilder.append("public class " + classSimpleName + " extends " + superClass.getCanonicalName() + " {\n");
this.usedTypes = new UsedTypesContext();
this.usedConverters = new UsedConvertersContext();
this.usedFilters = new UsedFiltersContext();
this.mappingContext = mappingContext;
this.usedMapperFacades = new UsedMapperFacadesContext();
this.logDetails = logDetails;
this.aggregateFieldMaps = new LinkedHashMap>();
}
private String makeUniqueClassName(String name) {
return name + System.nanoTime() + "$" + UNIQUE_CLASS_INDEX.getAndIncrement();
}
/**
* @return true if debug logging is enabled for this context
*/
public boolean isDebugEnabled() {
return logDetails != null;
}
public void debug(String msg) {
if (isDebugEnabled()) {
logDetails.append(msg);
}
}
public void debugField(FieldMap fieldMap, String msg) {
if (isDebugEnabled()) {
logDetails.append(fieldTag(fieldMap));
logDetails.append(msg);
}
}
public String fieldTag(FieldMap fieldMap) {
return "\n\t Field(" + fieldMap.getSource() + ", " + fieldMap.getDestination() + ") : ";
}
/**
* @return the StringBuilder containing the current accumulated source.
*/
protected StringBuilder getSourceBuilder() {
return sourceBuilder;
}
public Class getSuperClass() {
return superClass;
}
public String getClassSimpleName() {
return classSimpleName;
}
public String getPackageName() {
return packageName;
}
public String getClassName() {
return className;
}
List getFields() {
return fields;
}
List getMethods() {
return methods;
}
public boolean shouldMapNulls() {
return (Boolean) mappingContext.getProperty(Properties.SHOULD_MAP_NULLS);
}
public MappingContext getMappingContext() {
return mappingContext;
}
/**
* Adds a method definition to the class based on the provided source.
*
* @param methodSource
*/
public void addMethod(String methodSource) {
sourceBuilder.append("\n" + methodSource + "\n");
this.methods.add(methodSource);
}
/**
* Adds a field definition to the class based on the provided source.
*
* @param fieldSource
* the source from which to compile the field
*/
public void addField(String fieldSource) {
sourceBuilder.append("\n" + fieldSource + "\n");
this.fields.add(fieldSource);
}
/**
* @return the completed generated java source for the class.
*/
public String toSourceFile() {
return sourceBuilder.toString() + "\n}";
}
/**
* Compile and return the (generated) class; this will also cause the
* generated class to be detached from the class-pool, and any (optional)
* source and/or class files to be written.
*
* @return the (generated) compiled class
* @throws SourceCodeGenerationException
*/
protected Class compileClass() throws SourceCodeGenerationException {
try {
return compilerStrategy.compileClass(this);
} catch (SourceCodeGenerationException e) {
throw e;
}
}
/**
* @return a new instance of the (generated) compiled class
* @throws SourceCodeGenerationException
* @throws InstantiationException
* @throws IllegalAccessException
*/
@SuppressWarnings("unchecked")
public T getInstance() throws SourceCodeGenerationException, InstantiationException,
IllegalAccessException {
T instance = (T) compileClass().newInstance();
Type[] usedTypesArray = usedTypes.toArray();
Converter[] usedConvertersArray = usedConverters.toArray();
BoundMapperFacade[] usedMapperFacadesArray = usedMapperFacades.toArray();
Filter[] usedFiltersArray = usedFilters.toArray();
if (logDetails != null) {
if (usedTypesArray.length > 0) {
logDetails.append("\n\t" + Type.class.getSimpleName() + "s used: " + Arrays.toString(usedTypesArray));
}
if (usedConvertersArray.length > 0) {
logDetails.append("\n\t" + Converter.class.getSimpleName() + "s used: " + Arrays.toString(usedConvertersArray));
}
if (usedMapperFacadesArray.length > 0) {
logDetails.append("\n\t" + BoundMapperFacade.class.getSimpleName() + "s used: " + Arrays.toString(usedMapperFacadesArray));
}
if (usedFiltersArray.length > 0) {
logDetails.append("\n\t" + Filter.class.getSimpleName() + "s used: " + Arrays.toString(usedFiltersArray));
}
}
instance.setUsedTypes(usedTypesArray);
instance.setUsedConverters(usedConvertersArray);
instance.setUsedMapperFacades(usedMapperFacadesArray);
instance.setUsedFilters(usedFiltersArray);
return instance;
}
public String usedFilter(Filter filter) {
int index = usedFilters.getIndex(filter);
return "((" + Filter.class.getCanonicalName() + ")usedFilters[" + index + "])";
}
public String usedConverter(Converter converter) {
int index = usedConverters.getIndex(converter);
return "((" + Converter.class.getCanonicalName() + ")usedConverters[" + index + "])";
}
public String usedType(Type type) {
int index = usedTypes.getIndex(type);
return "((" + Type.class.getCanonicalName() + ")usedTypes[" + index + "])";
}
private String usedMapperFacadeCall(Type sourceType, Type destinationType) {
UsedMapperFacadesIndex usedFacade = usedMapperFacades.getIndex(sourceType, destinationType, mapperFactory);
String mapInDirection = usedFacade.isReversed ? "mapReverse" : "map";
return "((" + BoundMapperFacade.class.getCanonicalName() + ")usedMapperFacades[" + usedFacade.index + "])." + mapInDirection + "";
}
/**
* @param sourceType
* @param destinationType
* @param sourceExpression
* @param destExpression
* @return
*/
public String callMapper(Type sourceType, Type destinationType, String sourceExpression, String destExpression) {
return usedMapperFacadeCall(sourceType, destinationType) + "(" + sourceExpression + ", " + destExpression + ", mappingContext)";
}
/**
* @param sourceType
* @param destinationType
* @param sourceExpression
* @return
*/
public String callMapper(Type sourceType, Type destinationType, String sourceExpression) {
return usedMapperFacadeCall(sourceType, destinationType) + "(" + sourceExpression + ", mappingContext)";
}
/**
* @param source
* @param destination
* @return
*/
public String callMapper(VariableRef source, VariableRef destination) {
return callMapper(source.type(), destination.type(), "" + source, "" + destination);
}
/**
* @param source
* @param destination
* @return
*/
public String callMapper(VariableRef source, Type destination) {
return callMapper(source.type(), destination, "" + source);
}
public String usedMapperFacadeNewObjectCall(VariableRef source, VariableRef destination) {
return newObjectFromMapper(source.type(), destination.type());
}
public String newObjectFromMapper(Type sourceType, Type destinationType) {
UsedMapperFacadesIndex usedFacade = usedMapperFacades.getIndex(sourceType, destinationType, mapperFactory);
String instantiateMethod = usedFacade.isReversed ? "newObjectReverse" : "newObject";
return "((" + BoundMapperFacade.class.getCanonicalName() + ")usedMapperFacades[" + usedFacade.index + "])." + instantiateMethod
+ "";
}
/**
* Generates a code snippet to generate a new instance of the destination
* type from a mapper
*
* @param source
* @param destinationType
* @return a code snippet to generate a new instance of the destination type
* from a mapper
*/
public String newObjectFromMapper(VariableRef source, Type destinationType) {
return newObjectFromMapper(source.type(), destinationType) + "(" + source.asWrapper() + ", mappingContext)";
}
/**
* Generate usedType array index code for the provided variable
*
* @param r
* @return the code snippet for referencing a used type by it's array index
*/
public String usedType(VariableRef r) {
return usedType(r.type());
}
/**
* @param source
* @param destinationType
* @return the code snippet for generating a new instance, or assigning the
* default value in cases of primitive types
*/
public String newObject(VariableRef source, Type destinationType) {
if (destinationType.isPrimitive()) {
return VariableRef.getDefaultValue(destinationType.getRawType());
} else if (destinationType.isString()) {
return "null";
} else {
return newObjectFromMapper(source, destinationType);
}
}
/**
* Append a statement with assures that the container variable reference
* has an existing instance; if it does not, a new object is generated
* using MapperFacade.newObject
* @param fieldMap
* @return the code to assure the variable reference's instantiation
*/
public String assureContainerInstanceExists(FieldMap fieldMap) {
Property destination = fieldMap.getDestination();
Property source = fieldMap.getSource();
if (destination.getContainer() instanceof NestedProperty) {
VariableRef containerDestination = new VariableRef(destination.getContainer(), "destination");
VariableRef containerSource = new VariableRef(source.getContainer(), "source");
return assureInstanceExists(containerDestination, containerSource);
}
else {
return "";
}
}
/**
* Append a statement which assures that the variable reference has an
* existing instance; if it does not, a new object is generated using
* MapperFacade.newObject
*
* @param propertyRef
* the property or variable reference on which to check for an
* instance
* @param source
* @return a reference to this
SourceCodeBuilder
*/
public String assureInstanceExists(VariableRef propertyRef, VariableRef source) {
StringBuilder out = new StringBuilder();
String end = "";
if (source.isNullPossible()) {
out.append(source.ifNotNull());
out.append("{\n");
end = "\n}\n";
}
for (final VariableRef ref : propertyRef.getPath()) {
if (ref.isAssignable()) {
append(out, format("if((%s)) { \n", ref.isNull()), ref.assign(newObject(source, ref.type())), "}");
}
}
out.append(end);
return out.toString();
}
/**
* Appends the provided string as a source code statement, ending it with a
* statement terminator as appropriate.
*
* @param str
* @param args
* @return a reference to this
SourceCodeBuilder
*/
public static String statement(String str, Object... args) {
if (str != null && !"".equals(str.trim())) {
String expr = format(str, args);
String prefix = "";
String suffix = "";
if (!expr.startsWith("\n") || expr.startsWith("}")) {
prefix = "\n";
}
String trimmed = expr.trim();
if (!"".equals(trimmed) && !trimmed.endsWith(";") && !trimmed.endsWith("}") && !trimmed.endsWith("{") && !trimmed.endsWith("(")) {
suffix = "; ";
}
return prefix + expr + suffix;
} else if (str != null) {
return str;
}
return "";
}
/**
* Appends all of the String values provided to the StringBuilder in order,
* as "statements"
*
* @param out
* @param statements
*/
public static void append(StringBuilder out, String... statements) {
for (String statement : statements) {
out.append(statement(statement));
}
}
/**
* Join the items in the list together in a String, separated by the
* provided separator
*
* @param list
* @param separator
* @return a String which joins the items of the list
*/
public static String join(List list, String separator) {
StringBuilder result = new StringBuilder();
for (Object item : list) {
result.append(item + separator);
}
return result.length() > 0 ? result.substring(0, result.length() - separator.length()) : "";
}
/**
* Creates a VariableRef representing a Set for the provided
* VariableRef (which should be a Map)
*
* @param s
* the Map type variable ref
* @return a new VariableRef corresponding to an EntrySet for the provided
* variable ref, which should be a Map type
*/
public static VariableRef entrySetRef(VariableRef s) {
@SuppressWarnings("unchecked")
Type sourceEntryType = TypeFactory.valueOf(Set.class, MapEntry.entryType((Type>) s.type()));
return new VariableRef(sourceEntryType, s + ".entrySet()");
}
/**
* @param source
* @param dest
* @param srcNodes
* @param destNodes
* any relevant declared field mappings
* @return a code snippet suitable to use as an equality comparison test for
* the provided source and destination nodes
*/
public String currentElementComparator(Node source, Node dest, NodeList srcNodes, NodeList destNodes) {
StringBuilder comparator = new StringBuilder();
String or = "";
Set fieldMaps = new HashSet();
for (Node node : source.children) {
if (node.value != null) {
fieldMaps.add(node.value);
}
}
for (Node node : dest.children) {
if (node.value != null) {
fieldMaps.add(node.value);
}
}
Set comparisons = new HashSet();
for (FieldMap fieldMap : fieldMaps) {
if (!(fieldMap.is(aMultiOccurrenceElementMap()) && fieldMap.isByDefault()) && !fieldMap.isExcluded() && !fieldMap.isIgnored()) {
VariableRef sourceRef = getVariableForComparison(fieldMap, srcNodes, true, source);
VariableRef destRef = getVariableForComparison(fieldMap, destNodes, false, dest);
if (sourceRef != null && destRef != null) {
if (!sourceRef.isValidPropertyReference(propertyResolver)) {
throw new IllegalStateException(sourceRef + " is not valid!!");
} else if (!destRef.isValidPropertyReference(propertyResolver)) {
throw new IllegalStateException(destRef + " is not valid!!");
}
String code = this.compareFields(fieldMap, sourceRef, destRef, dest.elementRef.type(), null);
if (!"".equals(code) && comparisons.add(code)) {
comparator.append(or + "!(" + code + ")");
or = " || ";
}
}
}
}
return comparator.toString();
}
/**
* Resolves a VariableRef instance to be used for comparison;
*
* @param fieldMap
* @param nodes
* @param useSource
* @param defaultParent
* @return
*/
private VariableRef getVariableForComparison(FieldMap fieldMap, NodeList nodes, boolean useSource, Node defaultParent) {
VariableRef parentRef;
Node destNode = Node.findFieldMap(fieldMap, nodes, useSource);
Property prop = useSource ? fieldMap.getSource() : fieldMap.getDestination();
if (destNode != null && destNode.parent != null) {
parentRef = destNode.parent.elementRef;
if (isSelfOrParentComparison(destNode.parent, defaultParent)) {
return new VariableRef(prop.getElement(), parentRef);
} else {
return null;
}
} else if (prop.getContainer() != null) {
parentRef = defaultParent.elementRef;
return new VariableRef(parentRef.type(), parentRef.name());
} else {
/*
* Need to check whether the defaultParent.elementRef.type() can
* actually have a property of the specified type; if not, then it
* must be a reference to the outermost 'source' or 'destination'
* variables -- though we should have been able to properly detect
* this earlier...
*/
parentRef = defaultParent.elementRef;
if (propertyResolver.existsProperty(parentRef.type(), prop.getExpression())) {
return new VariableRef(prop, defaultParent.elementRef);
} else {
return new VariableRef(prop, useSource ? "source" : "destination");
}
}
}
private boolean isSelfOrParentComparison(Node node, Node reference) {
Node parent = reference;
while (parent != null) {
if (parent.elementRef.equals(node.elementRef)) {
return true;
} else {
parent = parent.parent;
}
}
return false;
}
private Property root(Property prop) {
Property root = prop;
while (root.getContainer() != null) {
root = root.getContainer();
}
return root;
}
/**
* Finds all field maps out of the provided set which are associated with
* the map passed in ( including that map itself); by "associated", we mean
* any mappings which are connected to the original FieldMap by having a
* matching source or destination, including transitive associations.
*
* @param fieldMaps
* the set of all field maps
* @param map
* the field map from which to start searching for reference
* @return a Set of FieldMaps which are associated; they must be mapped in
* parallel
*/
public Set getAssociatedMappings(Collection fieldMaps, FieldMap map) {
Set associated = new LinkedHashSet();
associated.add(map);
Set unprocessed = new LinkedHashSet(fieldMaps);
unprocessed.remove(map);
Set nextRoundSources = new LinkedHashSet();
Set nextRoundDestinations = new LinkedHashSet();
Set thisRoundSources = Collections.singleton(root(map.getSource()).getExpression());
Set thisRoundDestinations = Collections.singleton(root(map.getDestination()).getExpression());
while (!unprocessed.isEmpty() && !(thisRoundSources.isEmpty() && thisRoundDestinations.isEmpty())) {
Iterator iter = unprocessed.iterator();
while (iter.hasNext()) {
FieldMap f = iter.next();
boolean containsSource = thisRoundSources.contains(root(f.getSource()).getExpression());
boolean containsDestination = thisRoundDestinations.contains(root(f.getDestination()).getExpression());
if (containsSource && containsDestination) {
associated.add(f);
iter.remove();
} else if (containsSource) {
associated.add(f);
iter.remove();
nextRoundDestinations.add(f.getDestination().getName());
} else if (containsDestination) {
associated.add(f);
iter.remove();
nextRoundSources.add(f.getSource().getName());
}
}
thisRoundSources = nextRoundSources;
thisRoundDestinations = nextRoundDestinations;
nextRoundSources = new LinkedHashSet();
nextRoundDestinations = new LinkedHashSet();
}
return associated;
}
/**
* Tests whether any aggregate specifications apply for the specified
* FieldMap, and if so, adds it to the list of FieldMaps for that spec,
* returning true. Otherwise, false is returned.
*
* @param fieldMap
* @return true if aggregate specifications should be applied to the
* provided field map
*/
public boolean aggregateSpecsApply(FieldMap fieldMap) {
for (AggregateSpecification spec : codeGenerationStrategy.getAggregateSpecifications()) {
if (spec.appliesTo(fieldMap)) {
List fieldMaps = this.aggregateFieldMaps.get(spec);
if (fieldMaps == null) {
fieldMaps = new ArrayList();
this.aggregateFieldMaps.put(spec, fieldMaps);
}
fieldMaps.add(fieldMap);
return true;
}
}
return false;
}
/**
* @return the source code generated from applying all aggregated specs with
* accumulated FieldMaps to those FieldMap lists.
*/
public String mapAggregateFields() {
StringBuilder out = new StringBuilder();
for (Entry> entry : aggregateFieldMaps.entrySet()) {
if (!entry.getValue().isEmpty()) {
out.append(entry.getKey().generateMappingCode(entry.getValue(), this));
}
}
this.aggregateFieldMaps.clear();
return out.toString();
}
/**
* Generate the code necessary to process the provided FieldMap.
*
* @param fieldMap
* the FieldMap describing fields to be mapped
* @param source
* a variable reference to the source property
* @param destination
* a variable reference to the destination property
* @return a reference to this
CodeSourceBuilder
*/
public String mapFields(FieldMap fieldMap, VariableRef source, VariableRef destination) {
StringBuilder out = new StringBuilder();
StringBuilder closing = new StringBuilder();
if (destination.isAssignable() || destination.type().isMultiOccurrence() || !destination.type().isImmutable()) {
if (source.isNestedProperty()) {
out.append(source.ifPathNotNull());
out.append("{ \n");
closing.append("\n}");
}
boolean mapNulls = AbstractSpecification.shouldMapNulls(fieldMap, this);
if (destination.isNullPathPossible()) {
if (!source.isPrimitive()) {
if (!mapNulls) {
out.append(source.ifNotNull());
out.append(" {\n");
closing.append("\n}");
}
}
out.append(assureInstanceExists(destination, source));
}
Converter converter = getConverter(fieldMap, fieldMap.getConverterId());
source.setConverter(converter);
if (shouldCaptureFieldContext) {
beginCaptureFieldContext(out, fieldMap, source, destination);
}
StringBuilder filterClosing = new StringBuilder();
VariableRef[] filteredProperties = applyFilters(source, destination, out, filterClosing);
source = filteredProperties[0];
destination = filteredProperties[1];
for (Specification spec : codeGenerationStrategy.getSpecifications()) {
if (spec.appliesTo(fieldMap)) {
String code = spec.generateMappingCode(fieldMap, source, destination, this);
if (code == null || "".equals(code)) {
throw new IllegalStateException("empty code returned for spec " + spec + ", sourceProperty = " + source
+ ", destinationProperty = " + destination);
}
out.append(code);
break;
}
}
out.append(filterClosing);
if (shouldCaptureFieldContext) {
endCaptureFieldContext(out);
}
out.append(closing.toString());
}
return out.toString();
}
private void beginCaptureFieldContext(StringBuilder out, FieldMap fieldMap, VariableRef source, VariableRef dest) {
out.append(format("mappingContext.beginMappingField(\"%s\", %s, %s, \"%s\", %s, %s);\n" + "try{\n",
escapeQuotes(fieldMap.getSource().getExpression()), usedType(fieldMap.getAType()), source.asWrapper(),
escapeQuotes(fieldMap.getDestination().getExpression()), usedType(fieldMap.getBType()), dest.asWrapper()));
}
private void endCaptureFieldContext(StringBuilder out) {
out.append("} finally {\n" + "\tmappingContext.endMappingField();\n" + "}\n");
}
private String escapeQuotes(String string) {
return string.replaceAll("(? filter = getFilter(sourceProperty, destinationProperty);
if (filter != null) {
if (destinationProperty.isNestedProperty()) {
out.append("if (");
out.append(format("(%s && %s.shouldMap(%s, \"%s\", %s, %s, \"%s\", %s, mappingContext))", destinationProperty.pathNotNull(),
usedFilter(filter), usedType(sourceProperty.type()), varPath(sourceProperty), sourceProperty.asWrapper(),
usedType(destinationProperty.type()), varPath(destinationProperty), destinationProperty.asWrapper()));
out.append(" || ");
out.append(format("(%s && %s.shouldMap(%s, \"%s\", %s, %s, \"%s\", null, mappingContext))", destinationProperty.pathNull(),
usedFilter(filter), usedType(sourceProperty.type()), varPath(sourceProperty), sourceProperty.asWrapper(),
usedType(destinationProperty.type()), varPath(destinationProperty)));
out.append(") {");
} else {
out.append(format("if (%s.shouldMap(%s, \"%s\", %s, %s, \"%s\", %s, mappingContext)) {", usedFilter(filter),
usedType(sourceProperty.type()), varPath(sourceProperty), sourceProperty.asWrapper(),
usedType(destinationProperty.type()), varPath(destinationProperty), destinationProperty.asWrapper()));
}
sourceProperty = getSourceFilter(sourceProperty, destinationProperty, filter);
destinationProperty = getDestFilter(sourceProperty, destinationProperty, filter);
// need to set source property
closing.insert(0, "\n}\n");
}
return new VariableRef[] { sourceProperty, destinationProperty };
}
private static String varPath(VariableRef var) {
List path = var.getPath();
if (path.isEmpty()) {
return var.validVariableName();
} else {
return path.get(path.size() - 1).property().getExpression() + "." + var.validVariableName();
}
}
/**
* Proxies the destination property as necessary for filters that filter
* destination values.
*
* @param src
* @param dest
* @param filter
* @return
*/
private VariableRef getDestFilter(final VariableRef src, final VariableRef dest, final Filter filter) {
if (filter.filtersDestination()) {
return new VariableRef(dest.property(), dest.owner()) {
private String setter;
@Override
protected String setter() {
if (setter == null) {
String destinationValue = "%s";
if (dest.isPrimitive()) {
destinationValue = ClassUtil.getWrapperType(dest.rawType()).getCanonicalName() + ".valueOf(%s)";
}
String filteredValue = format("%s.filterDestination(%s, %s, \"%s\", %s, \"%s\", mappingContext)",
usedFilter(filter), destinationValue, usedType(src.type()), src.validVariableName(), usedType(dest.type()), dest.validVariableName()).replace(
"$$$", "%s");
setter = super.setter().replace("%s", dest.cast(filteredValue));
}
return setter;
}
};
}
return dest;
}
/**
* Proxies the source property as necessary for filters that filter source
* values.
*
* @param src
* @param dest
* @param filter
* @return
*/
private VariableRef getSourceFilter(final VariableRef src, final VariableRef dest, final Filter filter) {
if (filter.filtersSource()) {
return new VariableRef(src.property(), src.owner()) {
{
setConverter(src.getConverter());
}
private String getter;
@Override
protected String getter() {
if (getter == null) {
String sourceValue = super.getter();
if (src.isPrimitive()) {
sourceValue = ClassUtil.getWrapperType(src.rawType()).getCanonicalName() + ".valueOf(" + sourceValue + ")";
}
getter = src.cast(format("%s.filterSource(%s, %s, \"%s\", %s, \"%s\", mappingContext)", usedFilter(filter),
sourceValue, usedType(src.type()), src.validVariableName(), usedType(dest.type()), dest.validVariableName()));
}
return getter;
}
};
}
return src;
}
/**
* Locates all of the filters that apply to the specified source and
* destination properties, and creates a single aggregate filter from them,
* which is then returned.
* If no filters apply, then null is returned.
*
* @param sourceProperty
* @param destinationProperty
* @return
*/
private Filter getFilter(VariableRef sourceProperty, VariableRef destinationProperty) {
List> applicableFilters = new ArrayList>();
for (Filter filter : filters) {
if (filter.appliesTo(sourceProperty.property(), destinationProperty.property())) {
applicableFilters.add(filter);
}
}
if (applicableFilters.isEmpty()) {
return null;
} else if (applicableFilters.size() == 1) {
return applicableFilters.get(0);
} else {
return new AggregateFilter(applicableFilters);
}
}
/**
* Generates source code for an "equality" comparison of two variables,
* based on the FieldMap passed
*
* @param fieldMap
* @param sourceProperty
* @param destinationProperty
* @param destinationType
* @param logDetails
* @return the source code for equality test of the provided fields
*/
public String compareFields(FieldMap fieldMap, VariableRef sourceProperty, VariableRef destinationProperty, Type destinationType,
StringBuilder logDetails) {
StringBuilder out = new StringBuilder();
out.append("(");
if (sourceProperty.isNestedProperty()) {
out.append(sourceProperty.pathNotNull());
out.append(" && ");
}
if (destinationProperty.isNestedProperty()) {
if (!sourceProperty.isPrimitive()) {
out.append(sourceProperty.notNull());
out.append(" && ");
}
}
Converter converter = getConverter(fieldMap, fieldMap.getConverterId());
sourceProperty.setConverter(converter);
for (Specification spec : codeGenerationStrategy.getSpecifications()) {
if (spec.appliesTo(fieldMap)) {
String code = spec.generateEqualityTestCode(fieldMap, sourceProperty, destinationProperty, this);
if (code == null || "".equals(code)) {
throw new IllegalStateException("empty code returned for spec " + spec + ", sourceProperty = " + sourceProperty
+ ", destinationProperty = " + destinationProperty);
}
out.append(code);
break;
}
}
out.append(")");
return out.toString();
}
private Converter getConverter(FieldMap fieldMap, String converterId) {
Converter converter = null;
ConverterFactory converterFactory = mapperFactory.getConverterFactory();
if (converterId != null) {
converter = converterFactory.getConverter(converterId);
} else {
converter = converterFactory.getConverter(fieldMap.getSource().getType(), fieldMap.getDestination().getType());
}
return converter;
}
}