conjure-java icon indicating copy to clipboard operation
conjure-java copied to clipboard

Enum types in Java should implement some common interface

Open ilyanep opened this issue 7 years ago • 0 comments

Some sort of interface a la Enum<T>, call it ConjureEnum<T>. It doesn't even necessarily need the same methods (except for a values method as in #78, and the valueOf method [perhaps with the guarantee that ConjureEnum#valueOf applied to ConjureEnum#toString always gives you back the same value])

This is useful in a few cases where I want to write some utility code that applies generally to enums, but after replacing with Conjure enums, I have to either abandon that or pass two classes (the internal enum value class as well as the outer class).

Examples:

Getting string values for printing in a drop-down menu:

    public static <T extends Enum<T>> String[] valueNamesWithoutUnknown(Class<T> underlyingConjureEnumType) {
        List<String> ret = Lists.newArrayList();
        for (T val : underlyingConjureEnumType.getEnumConstants()) {
            if (!val.name().equals("UNKNOWN")) {
                ret.add(val.name());
            }
        }
        return ret.toArray(new String[0]);
    }

We frequently have enum classes with util methods that round-trip the values through an id (e.g. for serializing to a DB), so we unit test that the round-trip is stable:

public class PeeringConjureEnumUtilBaseTest<T, U extends Enum<U>> extends BaseTest {
    // <snip>
    protected final void testIdRoundTrips(Function<T, Long> getIdFunc,
                                Function<Long, T> fromIdFunc,
                                Class<U> clazz,
                                Function<U, T> fromEnumFunc) {
        try (AutoCloseableSoftAssertions softAssert = new AutoCloseableSoftAssertions()) {
            for (U enumValue : clazz.getEnumConstants()) {
                if (!enumValue.name().equals("UNKNOWN")) {
                    T value = fromEnumFunc.apply(enumValue);
                    try {
                        T roundTrippedValue = fromIdFunc.apply(getIdFunc.apply(value));
                        softAssert.assertThat(roundTrippedValue)
                                .as("Round-tripping through converters should provide same value.")
                                .isEqualTo(value);

                    } catch (Throwable e) {
                        softAssert.assertThat(true)
                                .as("Failed to roundtrip value %s with exception %s",
                                        value,
                                        e.getMessage())
                                .isFalse();
                    }
                }
            }
        }
}

In a property/configuration class where I know the values will take on the values of an enum and I want to validate that as well as return the right value:

    @Override
    public <T extends Enum<T>, U> U getEnum(PeerPropertyLookup remoteSystemId,
                                            NPPeerProperty prop,
                                            Class<T> enumClass,
                                            Class<U> returnClass) {
        Validate.isTrue(prop.getConjureEnumReturnType().equals(returnClass));
        // This validates that it's a valid entry in the enum.
        T enumReturn = getEnum(remoteSystemId, prop, enumClass);
        // This is a tricksy way to call #valueOf that doesn't rely on reflection but does rely on the fairly safe
        // assumption that Conjure will continue to make their enums Jackson-serializable in the future.
        try {
            return new ObjectMapper().readValue("\"" + enumReturn.name() + "\"", returnClass);
        } catch (IOException e) {
            throw new RuntimeException("Failed to parse enum for peer property " + prop + " into class " + returnClass.getName(),
                    e);
        }
    }

I think all of these would be significantly more simple and readable if I could rely on a <T extends ConjureEnum<T>> and I don't see a downside to doing so.

ilyanep avatar Oct 10 '18 19:10 ilyanep