public GeneratedMapperBase build(ClassMap<?, ?> classMap, MappingContext context) { StringBuilder logDetails = null; try { compilerStrategy.assureTypeIsAccessible(classMap.getAType().getRawType()); compilerStrategy.assureTypeIsAccessible(classMap.getBType().getRawType()); if (LOGGER.isDebugEnabled()) { logDetails = new StringBuilder(); String srcName = TypeFactory.nameOf(classMap.getAType(), classMap.getBType()); String dstName = TypeFactory.nameOf(classMap.getBType(), classMap.getAType()); logDetails.append("Generating new mapper for (" + srcName + ", " + dstName + ")"); } final SourceCodeContext mapperCode = new SourceCodeContext( classMap.getMapperClassName(), GeneratedMapperBase.class, context, logDetails); Set<FieldMap> mappedFields = new LinkedHashSet<FieldMap>(); mappedFields.addAll(addMapMethod(mapperCode, true, classMap, logDetails)); mappedFields.addAll(addMapMethod(mapperCode, false, classMap, logDetails)); GeneratedMapperBase instance = mapperCode.getInstance(); instance.setAType(classMap.getAType()); instance.setBType(classMap.getBType()); instance.setFavorsExtension(classMap.favorsExtension()); if (logDetails != null) { LOGGER.debug(logDetails.toString()); logDetails = null; } /* * Add a copy of the ClassMap to the current mapping context, which * only contains the field maps that were processed by this mapper * generation; this can later be used by ObjectFactory generation * when selecting a constructor -- since we only need a constructor * which handles the fields not mapped by the generated mapper */ classMap = classMap.copy(mappedFields); context.registerMapperGeneration(classMap); return instance; } catch (final Exception e) { if (logDetails != null) { /* * Print out the partial progress of the code generation, as it * can help to pinpoint the location of the internal error */ logDetails.append("\n<---- ERROR occurred here"); LOGGER.debug(logDetails.toString()); } throw new MappingException(e); } }
private String generateFieldMapCode( SourceCodeContext code, FieldMap fieldMap, ClassMap<?, ?> classMap, VariableRef destination, StringBuilder logDetails) throws Exception { final VariableRef sourceProperty = new VariableRef(fieldMap.getSource(), "source"); final VariableRef destinationProperty = new VariableRef(fieldMap.getDestination(), "destination"); destinationProperty.setOwner(destination); if (!sourceProperty.isReadable() || ((!destinationProperty.isAssignable()) && destinationProperty.type().isImmutable())) { if (logDetails != null) { code.debugField(fieldMap, "excluding because "); if (!sourceProperty.isReadable()) { Type<?> sourceType = classMap.getAType().equals(destination.type()) ? classMap.getBType() : classMap.getAType(); logDetails.append( sourceType + "." + fieldMap.getSource().getName() + "(" + fieldMap.getSource().getType() + ") is not readable"); } else { logDetails.append( destination.type() + "." + fieldMap.getDestination().getName() + "(" + fieldMap.getDestination().getType() + ") is not assignable and cannot be mapped in-place"); } } return ""; } // Make sure the source and destination types are accessible to the // builder compilerStrategy.assureTypeIsAccessible(sourceProperty.rawType()); compilerStrategy.assureTypeIsAccessible(destinationProperty.rawType()); return code.mapFields(fieldMap, sourceProperty, destinationProperty); }
private boolean isAlreadyExistsInUsedMappers(FieldMap fieldMap, ClassMap<?, ?> classMap) { Set<ClassMap<Object, Object>> usedClassMapSet = mapperFactory.lookupUsedClassMap(new MapperKey(classMap.getAType(), classMap.getBType())); if (!fieldMap.isByDefault()) { return false; } for (ClassMap<Object, Object> usedClassMap : usedClassMapSet) { for (FieldMap usedFieldMap : usedClassMap.getFieldsMapping()) { if (usedFieldMap.getSource().equals(fieldMap.getSource()) && usedFieldMap.getDestination().equals(fieldMap.getDestination())) { return true; } } } return false; }
private Set<FieldMap> addMapMethod( SourceCodeContext code, boolean aToB, ClassMap<?, ?> classMap, StringBuilder logDetails) { Set<FieldMap> mappedFields = new LinkedHashSet<FieldMap>(); if (logDetails != null) { if (aToB) { logDetails.append( "\n\t" + code.getClassSimpleName() + ".mapAToB(" + classMap.getAType() + ", " + classMap.getBTypeName() + ") {"); } else { logDetails.append( "\n\t" + code.getClassSimpleName() + ".mapBToA(" + classMap.getBType() + ", " + classMap.getATypeName() + ") {"); } } final StringBuilder out = new StringBuilder(); final String mapMethod = "map" + (aToB ? "AtoB" : "BtoA"); out.append("\tpublic void "); out.append(mapMethod); out.append( format( "(java.lang.Object a, java.lang.Object b, %s mappingContext) {\n\n", MappingContext.class.getCanonicalName())); VariableRef source; VariableRef destination; if (aToB) { source = new VariableRef(classMap.getAType(), "source"); destination = new VariableRef(classMap.getBType(), "destination"); } else { source = new VariableRef(classMap.getBType(), "source"); destination = new VariableRef(classMap.getAType(), "destination"); } append( out, format("super.%s(a, b, mappingContext);", mapMethod), "\n\n", "// sourceType: " + source.type() + source.declare("a"), "// destinationType: " + destination.type() + destination.declare("b"), "\n\n"); for (FieldMap currentFieldMap : classMap.getFieldsMapping()) { if (currentFieldMap.isExcluded()) { if (logDetails != null) { code.debugField(currentFieldMap, "excuding (explicitly)"); } continue; } if (isAlreadyExistsInUsedMappers(currentFieldMap, classMap)) { if (logDetails != null) { code.debugField( currentFieldMap, "excluding because it is already handled by another mapper in this hierarchy"); } continue; } FieldMap fieldMap = currentFieldMap; if (!aToB) { fieldMap = fieldMap.flip(); } if (!fieldMap.isIgnored()) { if (code.aggregateSpecsApply(fieldMap)) { continue; } try { mappedFields.add(currentFieldMap); String sourceCode = generateFieldMapCode(code, fieldMap, classMap, destination, logDetails); out.append(sourceCode); } catch (final Exception e) { MappingException me = new MappingException(e); me.setSourceProperty(fieldMap.getSource()); me.setDestinationProperty(fieldMap.getDestination()); me.setSourceType(source.type()); me.setDestinationType(destination.type()); throw me; } } else if (logDetails != null) { code.debugField(fieldMap, "ignored for this mapping direction"); } } out.append(code.mapAggregateFields()); out.append("\n\t\tif(customMapper != null) { \n\t\t\t customMapper.") .append(mapMethod) .append("(source, destination, mappingContext);\n\t\t}"); out.append("\n\t}"); if (logDetails != null) { logDetails.append("\n\t}"); } code.addMethod(out.toString()); return mappedFields; }
@SuppressWarnings({"unchecked"}) public <T, A, B> ConstructorMapping<T> resolve(ClassMap<A, B> classMap, Type<T> sourceType) { boolean aToB = classMap.getBType().equals(sourceType); Type<?> targetClass = aToB ? classMap.getBType() : classMap.getAType(); String[] declaredParameterNames = aToB ? classMap.getConstructorB() : classMap.getConstructorA(); Map<String, FieldMap> targetParameters = new LinkedHashMap<String, FieldMap>(); if (declaredParameterNames != null) { /* * An override to the property names was provided */ Set<FieldMap> fields = new HashSet<FieldMap>(classMap.getFieldsMapping()); for (String arg : declaredParameterNames) { Iterator<FieldMap> iter = fields.iterator(); while (iter.hasNext()) { FieldMap fieldMap = iter.next(); if (!fieldMap.is(aMappingOfTheRequiredClassProperty())) { if (!aToB) { fieldMap = fieldMap.flip(); } if (fieldMap.getSource().getName().equals(arg)) { targetParameters.put(arg, fieldMap); iter.remove(); } } } } } else { /* * Determine the set of constructor argument names * from the field mapping */ for (FieldMap fieldMap : classMap.getFieldsMapping()) { if (!fieldMap.is(aMappingOfTheRequiredClassProperty())) { if (!aToB) { fieldMap = fieldMap.flip(); } targetParameters.put(fieldMap.getDestination().getName(), fieldMap); } } } Constructor<T>[] constructors = (Constructor<T>[]) targetClass.getRawType().getConstructors(); TreeMap<Integer, ConstructorMapping<T>> constructorsByMatchedParams = new TreeMap<Integer, ConstructorMapping<T>>(); for (Constructor<T> constructor : constructors) { ConstructorMapping<T> constructorMapping = new ConstructorMapping<T>(); constructorMapping.setDeclaredParameters(declaredParameterNames); boolean byDefault = declaredParameterNames == null; try { /* * 1) A constructor's parameters are all matched by known parameter names * 2) ... */ String[] parameterNames = paranamer.lookupParameterNames(constructor); java.lang.reflect.Type[] genericParameterTypes = constructor.getGenericParameterTypes(); Type<?>[] parameterTypes = new Type[genericParameterTypes.length]; constructorMapping.setParameterNameInfoAvailable(true); if (targetParameters.keySet().containsAll(Arrays.asList(parameterNames))) { constructorMapping.setConstructor(constructor); for (int i = 0; i < parameterNames.length; ++i) { String parameterName = parameterNames[i]; parameterTypes[i] = TypeFactory.valueOf(genericParameterTypes[i]); FieldMap existingField = targetParameters.get(parameterName); FieldMap argumentMap = mapConstructorArgument(existingField, parameterTypes[i], byDefault); constructorMapping.getMappedFields().add(argumentMap); } constructorMapping.setParameterTypes(parameterTypes); constructorsByMatchedParams.put(parameterNames.length * 1000, constructorMapping); } } catch (ParameterNamesNotFoundException e) { /* * Could not find parameter names of the constructors; attempt to match constructors * based on the types of the destination properties */ List<FieldMap> targetTypes = new ArrayList<FieldMap>(targetParameters.values()); int matchScore = 0; int exactMatches = 0; java.lang.reflect.Type[] params = constructor.getGenericParameterTypes(); Type<?>[] parameterTypes = new Type[params.length]; if (targetTypes.size() >= parameterTypes.length) { for (int i = 0; i < params.length; ++i) { java.lang.reflect.Type param = params[i]; parameterTypes[i] = TypeFactory.valueOf(param); for (Iterator<FieldMap> iter = targetTypes.iterator(); iter.hasNext(); ) { FieldMap fieldMap = iter.next(); Type<?> targetType = fieldMap.getDestination().getType(); if ((parameterTypes[i].equals(targetType) && ++exactMatches != 0) || parameterTypes[i].isAssignableFrom(targetType)) { ++matchScore; String parameterName = fieldMap.getDestination().getName(); FieldMap existingField = targetParameters.get(parameterName); FieldMap argumentMap = mapConstructorArgument(existingField, parameterTypes[i], byDefault); constructorMapping.getMappedFields().add(argumentMap); iter.remove(); break; } } } constructorMapping.setParameterTypes(parameterTypes); constructorMapping.setConstructor(constructor); constructorMapping.setDeclaredParameters(declaredParameterNames); constructorsByMatchedParams.put((matchScore * 1000 + exactMatches), constructorMapping); } } } if (constructorsByMatchedParams.size() > 0) { return constructorsByMatchedParams.get(constructorsByMatchedParams.lastKey()); } else if (declaredParameterNames != null) { throw new IllegalArgumentException( "No constructors found for " + targetClass + " matching the specified constructor parameters " + Arrays.toString(declaredParameterNames) + (declaredParameterNames.length == 0 ? " (no-arg constructor)" : "")); } else { /* * User didn't specify any constructor, and we couldn't find any that seem compatible; * TODO: can we really do anything in this case? maybe we should just throw an error * describing some alternative options like creating a Converter or declaring their own * custom ObjectFactory... * */ ConstructorMapping<T> defaultMapping = new ConstructorMapping<T>(); defaultMapping.setConstructor(constructors.length == 0 ? null : constructors[0]); return defaultMapping; } }