Как обойти удаление типа Scala при рефлексивной обработке объектов Java? - PullRequest
0 голосов
/ 11 января 2019

У меня есть устаревший смешанный проект Scala / Java. Он имеет собственный инструмент отображения ORM для домашнего пивоварения, который генерирует несколько классов сущностей Java и затем работает с ними в Scala, отображая их из / в пользовательские карты свойств и файлы yaml.

Сегодня я наткнулся на проблему, заключающуюся в том, что инструмент ORM преобразует все строки внутри List<String> в карты Array[Byte] (что приводит к дальнейшему преобразованию данных в java.io.ByteArrayInputStream вместо простого списка строк Scala).

Пытаясь решить проблему и добавить List[String] к сопоставителю значений поля ORM, я получил бит по типу стирания. К сожалению, у меня нет контроля над классами Java, сгенерированными автоматическим инструментом, поэтому я не могу использовать TypeTags и т.д .; Кажется, отражение - моя единственная надежда.

Вот упрощенный пример проблемного варианта использования.

Java-объекты:

public class SubEntity {
}

import java.util.ArrayList;
import java.util.List;

public class Entity {

    protected List<SubEntity> field1;
    protected List<String> field2;

    public List<SubEntity> getField1() {
        if (field1 == null) {
            field1 = new ArrayList<SubEntity>();
        }
        return this.field1;
    }

    public List<String> getField2() {
        if (field2 == null) {
            field2 = new ArrayList<String>();
        }
        return this.field2;
    }
}

Часть проблемного кода Scala:

val ent = new Entity
  ent.getClass.getMethods
    .filter (m => m.getName.startsWith("get") && m.getName != "getClass")
    .filter (m => m.getParameterTypes.size == 0)
    .foreach (m => m.invoke(ent) match {
      case null => null
      // Not able to differentiate between List[String] and List[SomeEntity] and Array[Byte[
      // at runtime because of type erasure :(
      // How to solve this?
      case s: java.util.List[java.lang.String] => {
        println(s"Found list of strings in ${m.getName}, the values are ${s.toList}")
      }
      case s: java.util.List[SubEntity] => {
        println(s"Found list of SubEntity in ${m.getName}")
      }

      case _  => println(s"Unknown ${m.getName}")
  })

В зависимости от того, какой блок я комментирую, он печатает

 Found list of strings

или

 Found list of SubEntity

для обоих полей одновременно и не может правильно определить, что getField1 возвращает List[SubEntity], а getField2 возвращает List[java.lang.String].

Как мне исправить проблему и заставить матч работать по мере необходимости?

1 Ответ

0 голосов
/ 11 января 2019

Как-то мне удалось сделать это самому, с помощью статьи здесь: http://tutorials.jenkov.com/java-reflection/generics.html#returntypes

Я создал вспомогательный метод для проверки обобщенных значений возвращаемого типа и проверки, содержит ли он запрошенный аргумент:

  import java.lang.reflect.ParameterizedType

  def doesMethodReturnGenericSubtype(objectClass: Class[_], methodName: String,
                                      typeClassToCheck: Class[_]) = {

    val method = objectClass.getMethod(methodName)
    val returnType = method.getGenericReturnType

    // assume no type known
    var typeFound = false

    if (returnType.isInstanceOf[ParameterizedType]) {
      val pType = returnType.asInstanceOf[ParameterizedType]
      val typeArguments = pType.getActualTypeArguments
      for (typeArgument <- typeArguments) {
        val typeArgClass = typeArgument.asInstanceOf[Class[_]]
        if (typeArgClass.getName == typeClassToCheck.getName)
          typeFound = true
      }
    }

    typeFound
  }

Используя это так:

  val ent = new Entity
  ent.getClass.getMethods
    .filter (m => m.getName.startsWith("get") && m.getName != "getClass")
    .filter (m => m.getParameterTypes.size == 0)
    .foreach (m => m.invoke(ent) match {
      case null => null

      case s: java.util.Collection[_] if doesMethodReturnGenericSubtype(ent.getClass,
        m.getName, classOf[java.lang.String]) => {

        println(s"Found list of strings in ${m.getName}, the values are ${s.toList}")
      }

      case s: java.util.Collection[_] if doesMethodReturnGenericSubtype(ent.getClass,
        m.getName, classOf[SubEntity]) => {

        println(s"Found list of SubEntity in ${m.getName}")
      }

      case x  => println(s"Unknown ${m.getName} in ${x.getClass.getName}")
  })

Я все еще не уверен, как getActualTypeArguments может извлечь универсальный тип, если он был "стерт", но каким-то образом он работает просто отлично.

...