@Test public void shouldCustomizeClassMapBuilder() { // given MapperFactory mapperFactory = new DefaultMapperFactory.Builder() .classMapBuilderFactory(new ScoringClassMapBuilder.Factory()) .build(); // when ClassMap<Source, Destination> map = mapperFactory.classMap(Source.class, Destination.class).byDefault().toClassMap(); Map<String, String> mapping = new HashMap<String, String>(); for (FieldMap f : map.getFieldsMapping()) { mapping.put(f.getSource().getExpression(), f.getDestination().getExpression()); } // then assertThat("name.first").isEqualTo(mapping.get("firstName")); assertThat("name.last").isEqualTo(mapping.get("lastName")); assertThat("streetAddress").isEqualTo(mapping.get("postalAddress.street")); assertThat("countryCode").isEqualTo(mapping.get("postalAddress.country.alphaCode")); assertThat("currentAge").isEqualTo(mapping.get("age")); assertThat("birthState").isEqualTo(mapping.get("stateOfBirth")); assertThat(mapping.containsKey("driversLicenseNumber")).isFalse(); assertThat(mapping.containsKey("eyeColor")).isFalse(); }
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; } }