Mapstruct: как сопоставить A расширяет SingleValue <T>до T и A расширяет SingleValue <T>до B расширяет SingleValue <T>с тем же сопоставителем? - PullRequest
0 голосов
/ 22 февраля 2019

Я довольно долго смотрю на эту проблему и не могу найти ответ.Я пытаюсь использовать mapstruct для отображения объектов, у которых есть поля, которые являются реализациями универсальных объектов SingleValue.

Позвольте мне проиллюстрировать проблему на упрощенном примере.(броски опущены для читабельности)

У нас есть объект Single Value:

public abstract class SingleValue<T extends Serializable> implements Serializable {
    private T value;

    public SingleValue(T value) {
        this.value = value;
    }
}

Конкретный класс Single Value будет выглядеть следующим образом

public class FirstName extends SingleValue<String>{

}

И будетиспользуется в различных объектах, которые могут быть сопоставлены друг с другом

Отображения, которые должны быть возможны:

  • String -> FirstName
  • FirstName -> String
  • FirstName -> еще один расширяет объект SingleValue

Возможны следующие сопоставления с текущим решением:

  • String -> FirstName
  • FirstName ->Строка

Текущее сопоставление одного значения

@Mapper
public interface SingleValueMapper {
    SingleValueMapper INSTANCE = Mappers.getMapper(SingleValueMapper.class);
    default <Z extends Serializable, T extends SingleValue<Z>> T singleValue(Z obj, Class<Z> objClass, @TargetType Class<T> targetType) {
        if (obj == null) {
            return null;
        }
        return targetType.getConstructor(objClass).newInstance(obj);
    }

    default <T extends Serializable, Z extends SingleValue<T>> T getValue(Z singleValue) {
        if (singleValue == null) {
            return null;
        }
        return singleValue.getValue();
    }

    default <T extends SingleValue<String>> T singleValue(String obj, @TargetType Class<T> targetType) {
        return singleValue(obj, String.class, targetType);

    }
}

Для FirstName -> другое расширение сопоставления объекта SingleValue. Я нашел следующее решение, которое не работало:

default <T extends SingleValue<String>> T stringSingleValue(SingleValue<String> obj, @TargetType Class<T> targetType)
        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    return singleValue(obj.getValue(), String.class, targetType);
}

Проблемы с методом отображения, упомянутым выше:

  • Mapstruct предпочтет отображение getValue (Z singleValue) над этим в автоматическом выборе (я действительно не хочу использовать Qualifiers, потому что тогдаянеобходимо указать каждое поле, которое я сопоставляю)
  • Mapstruct будет считать метод сопоставления неоднозначным, если мы, например, добавим его для единичных значений UUID

работает, этот вызов генерируется mapstructкогда отображение String в A расширяет SingleValue:

A a = singleValueMapper.singleValue( client.getCompanyName(), A.class )

Этот вызов не выбирается mapstruct, но мы можем использовать его, когда вручную используем его с @Mapping в качестве выражения.Однако мы бы хотели, чтобы mapstruct автоматически отображала эти вызовы, когда отображение A расширяет SingleValue до B, расширяет SingleValue:

B b =singleValueMapper.stringSingleValue(command.getTitle(), B.class)

Есть ли способ осуществить это с помощью mapstruct и java generics?

Iиспользую Mapstruct версии 1.3.0-final.

1 Ответ

0 голосов
/ 11 марта 2019

Мне нужно место, чтобы оставить отзыв.

@Mapper // Why is this annotated with @Mapper or even an interface (iso a regular class)? Are there more methods in here not shown that should be generated by MapStruct?
public interface SingleValueMapper {
    SingleValueMapper INSTANCE = Mappers.getMapper(SingleValueMapper.class);

    // How should MapStruct know how to populate these? It can only map a source to a target? It takes @TargetType as hint, but it cannot populate the second argument Class<Z> ojbClass. But is this required? Perhaps obj.getClass()? 
    default <Z extends Serializable, T extends SingleValue<Z>> T singleValue(Z obj, Class<Z> objClass, @TargetType Class<T> targetType) {
        if (obj == null) {
            return null;
        }
        return targetType.getConstructor(objClass).newInstance(obj);
    }

    default <T extends Serializable, Z extends SingleValue<T>> T getValue(Z singleValue) {
        if (singleValue == null) {
            return null;
        }
        return singleValue.getValue();
    }

    // seems to me like a duplicate of the above?
    default <T extends SingleValue<String>> T singleValue(String obj, @TargetType Class<T> targetType) {
        return singleValue(obj, String.class, targetType);

    }
}

Итак, с учетом вышеизложенного, я бы переписал этот класс следующим образом:

public class SingleValueMapper {

    <Z extends Serializable, T extends SingleValue<Z>> T singleValue(Z obj,  @TargetType Class<T> targetType) {
        if (obj == null) {
            return null;
        }
        return targetType.getConstructor(obj.getClass()).newInstance(obj);
    }

    <T extends Serializable, Z extends SingleValue<T>> T getValue(Z singleValue) {
        if (singleValue == null) {
            return null;
        }
        return singleValue.getValue();
    }

}
...