views:

98

answers:

2

Hi, I'd like to create a generic enum-based mapper for IBatis. I'm doing this with the below code. This does have compile time errors, which I don't know how to fix. Maybe my solution is just plain wrong (keep in mind the use of IBatis), in such case please suggest something better.

Any help appreciated.

What I want to achieve is to define subsequent mappers as:

public class XEnumTypeHandler extends CommonEnumTypeHandler<X> {
}

The current code:

public class CommonEnumTypeHandler<T extends Enum> implements TypeHandlerCallback {

 public void setParameter(ParameterSetter ps, Object o) throws SQLException {
  if (o.getClass().isAssignableFrom(**T**)) { 
   ps.setString(((**T**) o).value().toUpperCase());
  } else
   throw new SQLException("Excpected ParameterType object than: " + o);
 }

 public Object getResult(ResultGetter rs) throws SQLException {
  Object o = valueOf(rs.getString());
  if (o == null)
   throw new SQLException("Unknown parameter type: " + rs.getString());
  return o;
 }

 public Object valueOf(String s) {
  for (T pt : T.**values()**) {
   if (pt.**value()**.equalsIgnoreCase(s))
    return pt;
  }
  return null;
 }
}

I've added error markings to the above code, the error messages are in order:

  • T cannot be resolved
  • The method value() is undefined for the type T
  • The method values() is undefined for the type T
  • The method values() is undefined for the type T

I've solved this issue with the following code:

public class CommonEnumTypeHandler<T extends Enum> implements TypeHandlerCallback {

    Class<T> clazz;

    public CommonEnumTypeHandler(Class<T> clazz) {
        this.clazz = clazz;
    }

    public void setParameter(ParameterSetter ps, Object o) throws SQLException {
        if (o.getClass().isAssignableFrom(clazz)) {
            ps.setString(((T) o).name().toUpperCase());
        } else
            throw new SQLException("Excpected " + clazz + " object than: " + o);
    }

    public Object getResult(ResultGetter rs) throws SQLException {
        Object o = valueOf(rs.getString());
        if (o == null)
            throw new SQLException("Unknown parameter type: " + rs.getString());
        return o;
    }

    public Object valueOf(String s) {
        return Enum.valueOf(clazz, s);
    }
}

Inheriting from this class I do:

public class SalesChannelTypeHandler extends CommonEnumTypeHandler<SalesChannel> {

    public SalesChannelTypeHandler() {
        super(SalesChannel.class);
    }

    public SalesChannelTypeHandler(Class<SalesChannel> clazz) {
        super(clazz);
    }

}
+5  A: 

I'm not sure what you're doing (a general overview in words would be nice), but:

  • You can't do isAssignableFrom(T) (you need a Class object), and you can't do instanceof T either (generics are non-reified). You may want to pass Class<T> type tokens instead.
  • Have you looked at EnumMap?

See also


It's still not clear what is desired, but perhaps it's something along the lines of this:

enum Color { BLACK, WHITE; }

public static void main(String[] args) {
    Color c = Enum.valueOf(Color.class, "black".toUpperCase());
    System.out.println(c); // prints "BLACK"
}

So we use Enum.valueOf that takes a type token Class<T extends Enum<T>>, and ask it for the enum constant with a given name. valueOf is NOT case-insensitive, but by conventions, all constants should be in uppercase, so we simply take the query string and turn it .toUpperCase().

polygenelubricants
EnumMap is not useful.What about valueOf errors?
Marcin Cylke
@Marcin: again, you can't do `T.values()`, but if you pass the type token for `Class<T>`, you'd be able to. I'll give an example, but I'm still not sure what you're doing.
polygenelubricants
Just trying to write a generic "from string to enum of defined type" converter. You pass in a string, say "test" and get enum TestEnum.test
Marcin Cylke
@Marcin: I understand what you're trying to achieve, but face it: [Generics are not reified in Java](http://stackoverflow.com/questions/1927789/why-should-i-care-that-java-doesnt-have-reified-generics)! You need to pass it as runtime method argument instead of as compiletime generic type. Maybe a constructor is useful?
BalusC
@Marcin: so you want a case-insensitive Enum.valueOf? http://java.sun.com/javase/6/docs/api/java/lang/Enum.html#valueOf%28java.lang.Class,%20java.lang.String%29
polygenelubricants
@BalusC Ok, I understand now, but I'm not sure what do you mean by passing Class<T> token. Could You show me some examples of that usage?
Marcin Cylke
@Marcin: essentially you pass instances of `Class` objects around. You either get this with the `.class` literal, or `Class.forName`, or `something.getClass()`, etc. These objects exist at run-time, so they can be used to support generics type system (which are non-reified).
polygenelubricants
+2  A: 

As pointed by Polygenelubricants, you need to pass concrete runtime types around, e.g. Class<?> instead of syntactic compiletime types like generic parameters. Here's a rewrite how you could use it:

public abstract class CommonEnumTypeHandler<E extends Enum<E>> implements TypeHandlerCallback {
    private Class<E> enumClass;

    public CommonEnumTypeHandler(Class<E> enumClass) {
        this.enumClass = enumClass;
    }

    public void setParameter(ParameterSetter ps, Object o) throws SQLException {
        if (enumClass.isInstance(o)) {
            ps.setString((enumClass.cast(o)).name().toUpperCase());
        } else {
            throw new SQLException("Excpected ParameterType object than: " + o);
        }
    }

    public Object getResult(ResultGetter rs) throws SQLException {
        try {
            return Enum.valueOf(enumClass, rs.getString());
        } catch (IllegalArgumentException e) {
            throw new SQLException("Unknown parameter type: " + rs.getString(), e);
        }
    }
}

Which you can then use as follows:

public class XEnumTypeHandler extends CommonEnumTypeHandler<X> {
    public XEnumTypeHandler() {
        super(X.class);
    }
}
BalusC
I'm still not sure what OP wants, but I'll upvote this anyway. I'm sure you're correct =)
polygenelubricants
In `setParameter`, change `isAssignableFrom` to `enumClass.isInstance( o )`
Alexander Pogrebnyak
@poly: He wants a generic enum <--> string converter for an object relational mapper. Java enum types aren't supported in JDBC. @Alex: updated!
BalusC
@BalusC: whatever skill allowed you to know that, I want to learn it.
polygenelubricants
@poly: [learn JDBC](http://java.sun.com/docs/books/tutorial/jdbc/index.html) and [DAO](http://balusc.blogspot.com/2008/07/dao-tutorial-data-layer.html) and try to refactor everything away and still keep it cross-DB-compatible. In other words: reinvent [Hibernate](http://hibernate.org) / [iBATIS](http://ibatis.apache.org/) / [JPA](http://java.sun.com/javaee/6/docs/tutorial/doc/bnbpz.html) ;)
BalusC
@polygenelubricants. Long usability meetings with customers. I arrived to similar solutions but Balus beat me by a few seconds.
Alexander Pogrebnyak