5

I have a web-application on Hibernate / Spring and I have few enums that I want to use in applications

public enum MerchantStatus {
    NEW("New"),
    ...

    private final String status;

    MerchantStatus(String status) {
        this.status = status;
    }

    public static MerchantStatus fromString(String status) {..}    

    public String toString() {..}
}

And

public enum EmployerType {
    COOL("Cool"),
    ...

    private final String type;

    EmployerType (String type) {
        this.type = type;
    }

    public static EmployerType fromString(String type) {..}    

    public String toString() {..}
}

I want to create converter to convert my enum objects to string and and vice versa. It is something like this:

public class MerchantStatusConverter implements AttributeConverter<MerchantStatus, String> {
    public String convertToDatabaseColumn(MerchantStatus value) {..}

    public MerchantStatus convertToEntityAttribute(String value) {..}
}

The problem is that I don't want to create converter for each enum and ideally it should be generic class/interface and I will use polymorphism here. The problem is that fromString is static method and it seems that it is impossible to create static method that returns generic type.

Are there any solutions of this problem?

Boris Parnikel
  • 863
  • 1
  • 7
  • 15
  • 1
    *it seems that it is impossible to create static method that returns generic type* - https://stackoverflow.com/questions/4409100/how-to-make-a-java-generic-method-static – BackSlash Jul 16 '17 at 14:56
  • I just wonder why you have to use both constructor and factory method? – phatnhse Jul 16 '17 at 14:59
  • Why do you want to create a converter for your enum? Hibernate should be able to handle this situation properly! Saving enum as strings to DB and converting string from DB to enum! You just have to use the @Enumerated annotation. Or it's not the case? – rafaelim Jul 16 '17 at 15:19
  • By the way, enums already can convert to and from their string representation without needing to store something extra (enum to string with `enumValue.name()` or `enumValue.toString()`; and string to enum with `YourEnumType.valueOf(string)` or `Enum.valueOf(clazz, string)`). The built-in string representation is how the enum is written exactly, which in your case is all-caps, whereas what you want is first-letter-capitalized, so maybe you can do some simple case conversion. – newacct Jul 28 '17 at 06:57

3 Answers3

5

The problem is that I don't want to create converter for each enum and ideally it should be generic class/interface and I will use polymorphism here.

You have no choice as your AttributeConverter implementation could not be parameterized when you annotate your entity.

You should indeed specify it only with the AttributeConverter class :

@Enumerated(EnumType.STRING)
@Convert(converter = MerchantStatusConverter.class)
private MerchantStatus merchantStatus;

But you could define an abstract class that defines the logic and subclassing it in each enum class.
To achieve it, you should introduce an interface in front of each enum class that declares a fromString() and a toString() method.

The interface :

public interface MyEnum<T extends MyEnum<T>>{

     T fromString(String type);
     String toString(T enumValue);
}

The enum that implements the interface :

public enum MerchantStatus implements MyEnum<MerchantStatus> {

    NEW("New"), ...


    @Override
    public MerchantStatus fromString(String type) {
     ...
    }

    @Override
    public String toString(MerchantStatus enumValue) {
     ...
    }
}

The abstract AttributeConverter class :

public abstract class AbstractAttributeConverter<E extends MyEnum<E>> implements AttributeConverter<E, String> {

    protected MyEnum<E> myEnum;

    @Override
    public String convertToDatabaseColumn(E attribute) {
        return myEnum.toString(attribute);
    }

    @Override
    public E convertToEntityAttribute(String dbData) {
        return myEnum.fromString(dbData);
    }
}

And concrete AttributeConverter class that needs to declare a public constructor to assign the protected myEnum field to an enum value (whatever of it):

public class MerchantStatusAttributeConverter extends AbstractAttributeConverter<MerchantStatus> {
   public MerchantStatusAttributeConverter(){
      myEnum = MerchantStatus.NEW; 
   }
}
davidxxx
  • 125,838
  • 23
  • 214
  • 215
2

If you want a general Converter for all your enum classes, you can use reflection, as long as you stick to a naming convention.

Your convention seem to be that you use toString() for enum -> String conversion, and a static fromString(String) for String -> enum conversion.

A Converter for that would be something like this:

public class EnumConverter<T extends Enum<T>> implements AttributeConverter<T, String> {
    private final Method fromStringMethod;

    public EnumConverter(Class<T> enumClass) {
        try {
            this.fromStringMethod = enumClass.getDeclaredMethod("fromString", String.class);
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
        if (! Modifier.isStatic(this.fromStringMethod.getModifiers()))
            throw new NoSuchMethodError("fromString(String) is not static");
        if (this.fromStringMethod.getReturnType() != enumClass)
            throw new NoSuchMethodError("fromString(String) does not return " + enumClass.getName());
    }

    public String convertToDatabaseColumn(T value) {
        return value.toString();
    }

    @SuppressWarnings("unchecked")
    public T convertToEntityAttribute(String value) {
        try {
            return (T) this.fromStringMethod.invoke(null, value);
        } catch (IllegalAccessException e) {
            throw new IllegalAccessError(e.getMessage());
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Error calling fromString(String): " + e, e);
        }
    }
}

You then construct it by naming the class, e.g.

new EnumConverter<>(MerchantStatus.class)
new EnumConverter<>(EmployerType.class)
Andreas
  • 154,647
  • 11
  • 152
  • 247
1

You should be able to do the following:

public class Converter<T extends Enum<T>, U> implements AttributeConverter<T, U> {

    public U convertToDatabaseColumn(T value) {
        ...
    }

    public T convertToEntityAttribute(U value) {
        ...
    }

}
Jacob G.
  • 28,856
  • 5
  • 62
  • 116