@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();
  }
Beispiel #2
0
  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);
  }
Beispiel #3
0
  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;
  }
Beispiel #4
0
  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);
    }
  }
Beispiel #5
0
  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;
    }
  }