Factory, Generics e Reflection

Olá!

Estou utilizando uma fábrica que utiliza reflection para criar implementações específicas da interface Manager. Manager é uma interface genérica parametrizada (utiliza type parameter).

public interface Manager <T extends Entity> { . . . }

ManagerFactory é uma fábrica de implementações específicas de Manager’s que estão de acordo com um certo tipo de Entity.

[code]public final class ManagerFactory {

. . .

/**
 * Cria sob demanda a implementação de <tt>Manager</tt> definida
 * para o tipo de <tt>Entity</tt>. . .
 */
public synchronized Manager getManager(final Class&lt? extends Entity&gt entity) {

   . . .

    Manager domainManager = null;

    if (cache.containsKey(domainManagerClass)) {
        return cache.get(domainManagerClass);

    } else {
        try {
            domainManager = domainManagerClass.newInstance();
        } catch (Exception ex) {
            throw new ManagerFactoryException(ex);
        } 
        cache.put(domainManagerClass, domainManager);
    }

    return domainManager;
}

}[/code]

O problema é que essa fábrica não específica o o parâmetro de tipo para Manager. Então, ao invés de retornar Manager<Cliente> clientManager = ...
ela retorna apenas Manager clientManager = ...
E isso é péssimo, pois eu perco as vantagens de usar Generic, que é type safety, e ganho uns três bilhões de warnings por todo o código.

Alguma luz?

Mude a assinatura para

[code]public final class ManagerFactory {

public synchronized <E extends Entity > Manager <E> (Class<E> entity) {
}[/code]

Valeu, Taborda.

A assinatura ficou assim: public synchronized &lt;E extends Entity&gt; Manager&lt;E, Long&gt; getManager(final Class&lt;E&gt; entity) { }

Mas mesmo assim continuo tendo as mesmas advertências graças à: domainManager = domainManagerClass.newInstance();

Será que há como colocar parâmetro de tipo numa instância criada por reflection?

Sem o resto do codigo fica difficil, por exemplo, que tipo é domainManagerClass ?

Se for Manager<E> o domainManagerClass.newInstance() deve retornar Manager<E> tb.

Tb tens que declarar <E> em todos os Manager dentro do método como em

   Manager&lt;E&gt; domainManager = null;

[quote=sergiotaborda]Tb tens que declarar <E> em todos os Manager dentro do método como em

   Manager&lt;E&gt; domainManager = null;

[/quote]
Eu fiz isso, Taborda.

A pedido, segue o código inteiro (mas creio que as outras partes não são muito relevantes). [code]
/**

  • Fábrica de <tt>Manager</tt>s.

  • Os Manager’s são armazenados em cache sob demanda, de modo que

  • só exista uma instância para cada Manager.

  • @author Rafael Fiume

  • @see DomainManager

  • @see ManagerClass
    */
    public final class ManagerFactory {

    /**

    • A única instância de <tt>ManangerFactory</tt>.
    • @see Singleton GoF patterns // PENDING colocar link
      */
      private static ManagerFactory factory;

    /**

    • Cache para os <tt>Manager</tt> instanciados através de
    • {@link #getManager(Class)}.
      */
      private static Map<Class, Manager> cache = new HashMap<Class, Manager>();

    private ManagerFactory() {
    super();
    }

    /**

    • Retorna uma instância de <tt>ManagerFactory</tt>.
      */
      public synchronized static ManagerFactory getInstance() {
      if (factory == null) {
      factory = new ManagerFactory();
      }
      return factory;
      }

    /**

    • Cria sob demanda a implementação de <tt>Manager</tt> definida

    • para o tipo de <tt>Entity</tt>. Depois de instanciado,

    • o <tt>Manager</tt> é armazenado em cache.

    • @param entity a entidade cujo <tt>Manager</tt> deverá ser criado.

    • @return Retorna implementação de <tt>Manager</tt> adeqüada ao tipo de

    •     &lt;tt&gt;iboi.domain.Entity&lt;/tt&gt;.
      
    • @throws DomainManagerFactoryException caso não exista um

    •     &lt;tt&gt;Manager&lt;/tt&gt; definido para o tipo de &lt;tt&gt;Entity&lt;/tt&gt;.
      
    • @see DomainManager

    • @see ManagerClass

    • @see iboi.domain.Entity
      */
      @SuppressWarnings("")
      public synchronized <E extends Entity> Manager<E, Long> getManager(final Class<E> entity) {

      if (!entity.isAnnotationPresent(ManagerClass.class)) {
      throw new ManagerFactoryException(
      "Não existe um EntityManager para essa entidade.");
      }

      final ManagerClass domainManagerClassAnnotation =
      entity.getAnnotation(ManagerClass.class);

      final Class&lt? extends Manager&gt domainManagerClass =
      domainManagerClassAnnotation.value();

      Manager<E, Long> domainManager = null;

      if (cache.containsKey(domainManagerClass)) {
      return cache.get(domainManagerClass);

      } else {
      try {
      // aqui gera warning
      domainManager = domainManagerClass.newInstance();
      } catch (Exception ex) {
      throw new ManagerFactoryException(ex);
      }
      cache.put(domainManagerClass, domainManager);
      }

      return domainManager;
      }
      }[/code]

Quando instancio um Manager via reflection, eu ainda obtenho um Manager sem parâmetro de tipo:

[quote]found : capture#7 of ? extends iboi.domain.service.manager.Manager
required: iboi.domain.service.manager.Manager<E,java.lang.Long>
domainManager = domainManagerClass.newInstance();[/quote]

Como visto, domainManagerClass é apenas uma instância de Class&lt? extends Manager&gt.

Alguma idéia?

Class &lt? extends Entity&gt não serve. Experimenta


              (...)

        final Manager&lt;E,Long&gt; domainManagerClass =
                (Manager&lt;E,Long&gt;)domainManagerClassAnnotation.value();

            (..)

Não podes usar ‘?’. O que tu queres é vincular todos os tipos Manager à entidade que passas com o argumento e que é referenciada por E.

[quote=sergiotaborda]Class &lt? extends Entity&gt não serve. Experimenta

              (...)

        final Manager&lt;E,Long&gt; domainManagerClass =
                (Manager&lt;E,Long&gt;)domainManagerClassAnnotation.value();

            (..)

Não podes usar ‘?’. O que tu queres é vincular todos os tipos Manager à entidade que passas com o argumento e que é referenciada por E.
[/quote]

Isso não vai dar certo, Taborda. domainManagerClass é uma instância de Class, não de Manager.

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ManagerClass { Class&lt? extends Manager&gt value(); }

Eu uso a anotação ManagerClass na entidade pare definir qual o seu Manager. <EDITADO> Isto é, qual a sua classe Manager, e não uma instância dessa classe. A instância eu quero obter pela factory. </EDITADO>

<EDITADO value=“de novo”>Onde você viu Class &lt? extends Entity&gt ??</EDITADO>

coloque

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ManagerClass { Class&lt;Manager&gt;? extends Entity&gt value(); }

E troque domainManagerClass para Class<Manager><E,Long>&gt

Boa sugestão!

Fiz as mudanças sugeridas, mas ainda não funcionou. Ao invés de warnings, estou com erro agora.

value em ManagerClass ficou um pouco diferente do que você propos para poder funcionar.

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ManagerClass { Class&lt? extends Manager&lt? extends Entity, Long&gt&gt value(); }

Class<Manager>&lt? extends Entity, Long&gt&gt faz mais sentido do que Class&lt? extends Manager&gt , que era como estava antes.

[code]public final class ManagerFactory {

. . .

public synchronized &lt;E extends Entity&gt; Manager&lt;E, Long&gt; getManager(final Class&lt;E&gt; entity) {

    if (!entity.isAnnotationPresent(ManagerClass.class)) {
        throw new ManagerFactoryException(
                "Não existe um EntityManager para essa entidade.");
    }

    final ManagerClass domainManagerClassAnnotation =
            entity.getAnnotation(ManagerClass.class);

    final Class&lt;Manager&gt;&lt;E, Long&gt;&gt domainManagerClass =
            domainManagerClassAnnotation.value();

    Manager&lt;E, Long&gt; domainManager = null;

    if (cache.containsKey(domainManagerClass)) {
        return (Manager&lt;E, Long&gt;) cache.get(domainManagerClass);

    } else {
        try {
            domainManager = domainManagerClass.newInstance();
        } catch (Exception ex) {
            throw new ManagerFactoryException(ex);
        } 
        cache.put(domainManagerClass, domainManager);
    }

    return domainManager;
}

}[/code]

De novo, Class<Manager><E, Long>&gt domainManagerClass… está mais exato do que antes, Class&lt? extends Manager&gt domainManagerClass…

Porém, [quote=compilador]found : java.lang.Class<capture#886 of ? extends iboi.domain.service.manager.Manager>&lt? extends iboi.domain.Entity,java.lang.Long&gt&gt
required: java.lang.Class<iboi.domain.service.manager.Manager><E,java.lang.Long>&gt
domainManagerClassAnnotation.value();[/quote]
O compilador está embaçando com o <E extends Entity> na assinatura de getManager, dizendo que é diferente do &lt? extends Entity&gt de value em ManagerClass. :shock:

Pra mim era óbvio que as duas coisas são o mesmo, mas o mundo (e a JDK) não são cheios de surpresas?

[quote=RafaelRio]Porém, [quote=compilador]found : java.lang.Class<capture#886 of ? extends iboi.domain.service.manager.Manager>&lt? extends iboi.domain.Entity,java.lang.Long&gt&gt
required: java.lang.Class<iboi.domain.service.manager.Manager><E,java.lang.Long>&gt
domainManagerClassAnnotation.value();[/quote]
O compilador está embaçando com o <E extends Entity> na assinatura de getManager, dizendo que é diferente do &lt? extends Entity&gt de value em ManagerClass. :shock:

Pra mim era óbvio que as duas coisas são o mesmo, mas o mundo (e a JDK) não são cheios de surpresas?[/quote]
Agora ficou mais claro o porquê disso. O parâmetro de tipo em <E extends Entity> de getManager pode ser diferente do parâmetro de tipo em &lt? extends Entity&gt de ManagerClass. Isso estava na minha cara! O compilador não tem como verificar duas coisas diferentes que vão ser definidas em tempo de execução.

O erro eu solucionei com Class&lt? extends Manager&gt domainManagerClass…. Ou seja, de volta a estaca zero. :cry:

Será que tem jeito?

Aehh!! Não é que tem jeito?! :smiley:

Pra fazer funcionar declarei

private final Manager&lt;E, Long&gt; manager;

e incializei com a factory invocando o método com o argumento de tipo

ManagerFactory.getInstance().&lt;E&gt;getManager(entityClass);

A idéia é pedir “me dê um gerenciador para tal entidade”, assim o cliente não precisa saber quem é a implementação do manager para tal entidade. Esses detalhes ficam com a factory. Ao mesmo tempo, usar classes genéricas, seguir o mantra open for extension, closed for modification e utilizar reflection na factory para evitar montes de if/elses.

[quote=RafaelRio]Aehh!! Não é que tem jeito?! :smiley:
[/quote]

Parabéns. Desculpe não ter continuado o dialogo , mas estou meio sem tempo. Vc reparou que usa um Long como tipo generico fixo (ou seja, é sempre um long e não extends Long) ? Isso significa que na realidade isso é um tipo estático :wink:
Se as suas chaves são sempre Long não precisa definir o tipo como generico.
Em opção elas podem ser de uma certa interface. vc usa essa interface estáticamente e pode implementá-la com Long ou qq outro objeto. Pense nisso…

Tranqüilo, Taborda! Mesmo porque ainda não está tudo resolvido (diria que 50%) apesar das minhas vibrações. O cliente que usa a factory está ok. No entanto, ainda estou com problemas em mapear o manager da anotação para a fábrica e instanciar o manager correto via reflection (isso eu ainda não sei se tem jeito) :(. Também tenho que arranjar outra forma de declarar e inicializar o cache. :roll:

[quote=sergiotaborda]Vc reparou que usa um Long como tipo generico fixo (ou seja, é sempre um long e não extends Long) ? Isso significa que na realidade isso é um tipo estático :wink:
Se as suas chaves são sempre Long não precisa definir o tipo como generico.[/quote]
Tava achando estranho você não ter comentado ainda sobre isso. :lol: Eu taquei Long aí pra simplificar, mas vou ter que definir qual o tipo da chave primária também.

A verdade é que eu ainda não decidi exatamente o que fazer com a chave primária. Se eu seguir sua sugestão, todas as entidades terão que ter ID tipo Long; se eu precisar definir o tipo como argumento na factory, terei que expor os clientes a esse tipo de detalhe…

Mas isso é o de menos agora, não estou preocupado. A prioridade é fazer a coisa funcionar direito (eliminar todos os warnings), e, conforme eu for progredindo, eu escrevo aqui. É claro que estou interessado em sugestões! :stuck_out_tongue: