Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generics: how to get a generic type from a method?

Although my question is about Java generics, I've put some JPA related code to show you the real context.

I'm working with JPA 2.0 and Criteria API based queries. All my queries follow the same pattern (comparing simple attributes; no path navigation needed), so I'm trying to write a generic class to deal with JPA while keeping the business logic in separated classes. My goal is to have a method that, given an Entity type and a Map that stores the pairs (field name -> desired value) defining the criteria, returns a bean (or collection of beans) with the value of some of the entity fields.

All my entities implement the Persistible interface, while all my transfer objects inherit from QueryBean. I consider those classes not related to the problem, so I'm skipping their code.

The following code is a snippet of my first approach (please assume cb is a valid CriteriaBuilder instance):

protected <T extends QueryBean, TT extends Persistible> Collection<T> executeQueryEntity
        (Class<T> type, Class<TT> rootType, QueryConfig queryConfig, Map<String, Object> parameters) {
    // (...) Initialization goes here

    CriteriaQuery<T> c = cb.createQuery(type);

    // FROM CLAUSE
    Root<TT> root = c.from(rootType);

    // SELECT CLAUSE
    List<String> constructorParams = queryConfig.getTargetAttributes();
    Selection<?>[] selectArray = new Selection<?>[constructorParams.size()];
    for (int i = 0; i < constructorParams.size(); i++) {
        selectArray[i] = root.get(constructorParams.get(i));
    }
    c.select(cb.construct(type, selectArray));

    // WHERE CLAUSE
    for (String filter : parameters.keySet()) {
        if (queryConfig.getFieldConfiguration().get(filter).compareUsingEquals()) {
            // SOME PREDICATE
        }
        else {
            // SOME OTHER PREDICATE
        }
    }

    // (...) Rest of the code goes here
}

My QueryConfig interface is as follows:

public interface QueryConfig {

List<String> getTargetAttributes();
Map<String, FieldConfiguration> getFieldConfiguration();

}

Since I'm already using a QueryConfig class which provides information about the query (such as parameters needed for the QueryBean constructor or information about the entity fields), I thought it would be nice to get the entity type from that class instead of passing it as a Class parameter. I infer from this question that it can't be done directly, so I've tried the following workaround:

Adding a method to QueryConfig like this:

Class< ? extends Persistible> getTargetEntity();

Adding an intermediate method like this:

public <T extends QueryBean> Collection<T> queryMany(Class<T> type, QueryConfig config, Map<String, Object> parameters) {
    executeQueryEntity(type, config.getTargetEntity(), parameters);
}

But it won't compile. I believe the reasons are explained here: Type mismatch for Class Generics but I actually didn't understand the answer. My questions are: is there a way to avoid passing the Class< TT > parameter to the execute method? Is this a good way to deal with the problem or should I refactor the whole thing? Any improvements to the code are welcome as well!

like image 232
DiegoAlfonso Avatar asked Apr 24 '13 03:04

DiegoAlfonso


1 Answers

I don't quite get the reason why you think your approach will work: QueryConfig with Class< ? extends Persistible> getTargetEntity(); give no type information that can be referred by compiler, and why do you think the compiler can "guess" the type you are going to return, and do the type checking for you?

One way you can do is providing type information in QueryConfig

public interface QueryConfig <T extends Persistable> {
  Class<T> getTargetEntity();
  List<String> getTargetAttributes();
  Map<String, FieldConfiguration> getFieldConfiguration();
}

and your queryMany method can be like:

public <T extends QueryBean, TT extends Persistible> 
Collection<T> queryMany(Class<T> type, 
                        QueryConfig<TT> config, 
                        Map<String, Object> parameters) {
    executeQueryEntity(type, config.getTargetEntity(), parameters);
}
like image 99
Adrian Shum Avatar answered Sep 27 '22 17:09

Adrian Shum