publicissapient-france/selma

Custom mappers which are SELMA mappers are not injected into the outer mapper

tucu00 opened this issue · 2 comments

If I understood the documentation correctly, the following should work:

@Mapper
public abstract class XMapper {
  public abstract String toString(String s);
}

@Mapper(withCustom = XMapper.class)
public abstract class YMapper {
  public abstract List<String> toString(List<String> list);
}

  public static void main(String... args) {
    YMapper mapper = Selma.builder(YMapper.class).build();
    List<String> list = new ArrayList<>();
    list.add("a");
    list = mapper.toString(list);
    System.out.println(list);
  }

However, I'm getting a NullPointerException because in the generated YMapper the custom mapper XMapper is NULL.

My understanding was that Selma.build() would inject a XMapper instance in the generated YMapper instance.

If using a custom mapper that is a concrete class with a default constructor the same is instantiated in the generated 'YMapper' class constructor.

So far the only solution I've found is to cast the YMapper to the generated class and set the an XMapper instance previously obtained. But this does not seem correct, not to mention that for composite Mappers I have to do it recursively and all using reflection as I cannot cast to a generated class because javac fails to find it.

Hi,

in this case XMapper is an abstract class so Selma does not know how to instantiate it by default.
But you can provide the instance when building the YMapper.

Selma.builder(YMapper.class).withCustom(Selma.mapper(XMapper.class)).build();

When the Custom Mapper is a class with default constructor available, Selma will instantiate it for you.

Hi, Sorry for the long hiatus following this up. What I was suggesting was Selma to be a bit more proactive about finding the Mappers (with default constructor and/or Selma generated). Currently I'm using the following code to do that and it works out nicely. It would be nice if Selma does something like that:

/**

  • Returns a Selma Mapper that has all its custom mappers, transitively, instantiated and injected if
  • the custom mappers are Selma Mappers and/or have a default constructor.
    */
    public static T build(Class mapperClass) {
    T mapper;
    if (mapperClass.isInterface() || (mapperClass.getModifiers() & Modifier.ABSTRACT) == Modifier.ABSTRACT) {
    // if interface or abstract, we assume it is a Selma generated Mapper.
    mapper = Selma.builder(mapperClass).build();
    injectCustomMappers(mapper);
    } else {
    // not abstract, if it has a default constructor we instantiate it
    try {
    Constructor constructor = mapperClass.getConstructor();
    mapper = constructor.newInstance();
    } catch (NoSuchMethodException ex) {
    // we could not instantiate it, we return null.
    mapper = null;
    } catch (Exception ex) {
    throw new RuntimeException(ex);
    }
    }
    return mapper;
    }

static void injectCustomMappers(T mapper) {
for (Class customMapperClass : findCustomMappers(mapper.getClass())) {
Object customMapper = build(customMapperClass);
injectCustomMapper(mapper, customMapperClass, customMapper);
}
}

static List findCustomMappers(Class mapperClass) {
List classes = new ArrayList();
for (Field field : mapperClass.getDeclaredFields()) {
if (field.getName().startsWith("customMapper")) {
classes.add(field.getType());
}
}
return classes;
}

static void injectCustomMapper(Object mapper, Class customMapperClass, Object customMapper) {
try {
Method method = mapper.getClass().getMethod(
"setCustomMapper" + customMapperClass.getSimpleName(),
customMapperClass
);
method.invoke(mapper, customMapper);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}